Compare commits

..

44 Commits

Author SHA1 Message Date
Mihir Kandoi
b3d55db893 fix(stock): return material that a document rejected in full (#59280)
* fix(stock): return material that a document rejected in full

A receipt may leave the accepted quantity at zero when the whole row is
rejected, and so may an invoice that updates stock. Neither could be sent back:
the return mapper kept only rows with an accepted quantity, so it produced a
document with no rows at all, and the checks that a return carries something
looked at the accepted quantity alone.

Carry a row that has a rejected quantity, and count that quantity as material
going back.

* test(stock): cover the return of a receipt that rejected every unit

* fix(stock): send rejected material back at the rate it came in at

Material a transfer rejected in full went back to the in-transit warehouse with
no value at all, and the invoice, having no accepted quantity to price, fell
back to a single unit's rate.

Three things stood in the way of simply reversing the rate the material came in
at. A return of a transfer never read that rate. The value going back counted
the accepted quantity alone. And the rate was looked up against the accepted
warehouse, which had received nothing.

Ten units received at 100 into the rejected warehouse now go back as 1000, and
the entries say the same as the stock.

* test(accounts): cover the return of a transfer that rejected every unit
2026-09-22 20:32:13 +05:30
Mihir Kandoi
876bf0b7f7 fix(stock): move rejected material of an internal transfer on an invoice too (#59260)
* feat(stock): move rejected material of an internal transfer on an invoice too

An invoice that updates stock moves the same material as a receipt, so it was
left with the fault the layers below fixed for the receipt: the rejected
material stayed in the in-transit warehouse and was counted in the rejected
warehouse as well.

Its entries had no accounting for rejected material at all, which is why those
layers stopped at the receipt. The entry that books the rejected warehouse
serves both cases now: an internal transfer credits the in-transit warehouse for
the accepted and the rejected material together, and an invoice that bills the
rejected qty keeps its cost on the supplier entry, as before.

`is_internal_receipt` covers an invoice that updates stock, so the qty, the
packages, cancelling and returning behave as they do on a receipt.

* test(accounts): cover an internal transfer invoice that rejects material

* fix(stock): validate a rejected package against the rejected quantity

On an invoice row the quantity field was forced to `stock_qty` for every
package, so the package of rejected material was compared with the accepted
quantity and a partly rejected internal transfer could not be saved.

Keep the override for the accepted package only, and let an explicitly passed
field through untouched.

* test(accounts): cover rejected batch material on an internal transfer invoice

* fix(accounts): let a stock updating invoice reject every unit of a row

A receipt may leave the accepted quantity at zero when the whole row is
rejected. An invoice that updates stock moves the same material, but the row was
refused with "Quantity for Item cannot be zero".

Share the receipt test with `is_internal_receipt()` so both exemptions follow
one rule.

* test(accounts): cover a stock updating invoice that rejects a whole row

* fix(stock): count a rejected package in the units a package counts in

A package holds stock units, but the package of rejected material was compared
with the rejected quantity as the row states it. A row of one box of twelve was
refused for holding twelve units, and resizing such a package cut it to the
number of boxes.

Read the row the way the package was built, so a rejected quantity is carried
through the conversion factor like every other quantity.

* test(stock): cover rejected material of a transfer bought in another unit
2026-09-22 20:32:13 +05:30
Mihir Kandoi
16b1be814c fix(accounts): bill the rejected quantity on a stock updating invoice (#59258)
* feat(accounts): bill the rejected quantity on a stock updating invoice

Both rejected material settings are written for the receipt flow. The receipt
books rejected material against Stock Received But Not Billed, and the invoice
mapped from it carries the received qty with nothing rejected, so the supplier
pays for every unit received and that account clears.

An invoice that moves stock itself has no receipt to do that. It bills the
accepted qty alone, so the setting that asks for rejected material to be valued
had nothing to back the value it asked for, and the layer below zeroes it.

Such an invoice bills the received qty now when the setting is on, spreads the
valuation over the same qty, and debits the rejected warehouse from its own
entries, so the supplier entry carries the cost. An internal transfer bills
nothing of the sort: its material is paid for by the warehouse it came out of.

* test(accounts): cover the rejected quantity billed on a stock updating invoice

* fix(accounts): tell the form whether the rejected quantity is billed

The form read the two settings off the document, where they never appear: a
doctype settings map drives the settings panel of a form, it does not put those
fields on the document. The amount of a row stayed at the accepted qty until the
document was saved and the server worked it out again.

Both settings go into the boot now, and the form reads them from there. An
internal transfer is left alone, as it is on the server.

* test(accounts): give the project purchase cost test its own payable account

The test bills in USD but let the payable account be chosen for it, so it passed
only when an earlier test had left a payable account in that currency behind.

* test(accounts): read the invoices back before cancelling them

Submitting an invoice against a project writes to it again, so the copy the test
holds is already behind and cancelling it fails on the timestamp.

* test(accounts): restore the buying settings the tests change

Both tests put the settings back by hand, one of them to a value the site never
had, so every test that ran afterwards saw the rejected quantity billed. Let the
suite save and restore them.

* fix(accounts): spread a discount over the quantity the invoice bills

An invoice that bills the rejected quantity carries an amount for every unit
received, but the net rate was still divided by the accepted quantity alone. Six
accepted and four rejected at 100 with a tenth off gave a net rate of 150 and
wrote that to the item as its last purchase rate.

Divide by the quantity the amount was built from. Nothing changes for a document
that does not bill the rejected quantity.

* test(accounts): cover a discount on an invoice that bills the rejected quantity

* fix(accounts): book the rejected warehouse from the stock it received

The entry was written only while the setting that bills the rejected quantity
was on, so an invoice reposted after that setting changed lost the entry while
keeping the supplier credit that paid for the material, and reposting failed on
the difference. The stock the invoice moved is what the entry records, so read
that instead. Material that nothing paid for carries no value and still books
nothing.

A return that stands on its own compared what the supplier was credited with the
stock of the accepted warehouse alone, and booked the rejected material a second
time as a variance. Count both warehouses, since both come back.

* test(accounts): cover the rejected warehouse after a repost and on a return

* docs(buying): say where the rejected quantity is billed

An invoice that updates stock bills the rejected quantity too, with no receipt
in front of it.

* docs(buying): drop the list of documents from the billing description

The purchase cycle says it.
2026-09-22 20:32:12 +05:30
Mihir Kandoi
ecc643fde0 fix(stock): stop valuing rejected material on a stock updating invoice (#59257)
* fix(stock): stop valuing rejected material on a stock updating invoice

`set_valuation_rate_for_rejected_materials` is written for the receipt flow. A
receipt books rejected material against Stock Received But Not Billed, and the
invoice mapped from it carries the received qty with nothing rejected, so the
supplier pays for all of it and that account clears.

An invoice that moves stock itself has no receipt to do that. It bills the
accepted qty alone, while the setting still gave its rejected material the
invoice rate, so the rejected warehouse received stock value that nothing paid
for and no entry backed.

One predicate now answers whether rejected material carries value, for plain
rows and for rows tracked by a package alike. Material of an internal transfer
always does, since its value was credited out of the in-transit warehouse. A
receipt follows the setting. A stock updating invoice does not, until it bills
that material.

* test(accounts): cover rejected material value on a stock updating invoice

* fix(stock): read the transit test from where this layer keeps it

The layer below asks the package itself whether the material came from an
in-transit warehouse. Here that question is answered by the buying settings.

* docs(buying): say where rejected material is valued

The setting reaches every document of the purchase cycle that receives material,
not the receipt alone.
2026-09-22 20:32:12 +05:30
Mihir Kandoi
df3f952fac fix(stock): empty the in-transit warehouse when every unit is rejected (#59256)
* fix(stock): deduct rejected qty from the in-transit warehouse

On an internal transfer the receipt took only the accepted qty out of the
in-transit warehouse, while the rejected qty was booked into the rejected
warehouse, so the rejected material was counted in both.

It also carried stock value, because an internal transfer anchors every inward
entry to the rate of the delivery note, but the rejected warehouse got no
accounting entry unless Buying Settings asked for one. The stock value and the
account value then disagreed.

The entry for the in-transit warehouse covers the accepted and the rejected qty
now, and the rejected warehouse is booked whatever that setting says, since the
value came out of the in-transit warehouse either way.

* test(stock): cover rejected qty on an internal transfer receipt

* fix(stock): let rejected serial and batch material leave the in-transit warehouse

A serial or batch item rejected on an internal transfer could not be received at
all. The package for the in-transit warehouse is copied from the delivery note,
and the copy was never resized, because the check compared a positive qty against
the negative total of an outgoing package.

The package of a row follows the split now. A row that rejects material carries
the package of its accepted warehouse, holding the accepted material alone,
which is the entry it belongs to and the total the desk sets its accepted qty
from. A row that rejects nothing keeps the package of the in-transit warehouse it
came out of. Editing the split moves the package from one to the other, and a row
that accepts nothing carries no package at all.

The entry for the in-transit warehouse gets a package of its own, holding the
accepted and the rejected material together. A landed cost voucher or a repost
reuses it rather than building a second one, which would make the batch qty count
the material twice.

Rejected material of an internal transfer keeps its rate, since its value was
credited out of the in-transit warehouse; refusing it a rate left the difference
to be written off. Cancelling reverses that warehouse with the package its entry
posted, after the rejected warehouse, so serial numbers are not put back and
taken out again. A return builds an inward package covering both.

The resize also fixes an ordinary partial receipt of a tracked item bought in
another UOM: the package is sized in stock UOM, which is what the row is
validated against.

* test(stock): cover rejected serial and batch material on an internal transfer

* fix(stock): build the package of rejected material on an internal transfer

A receipt of an internal transfer builds one package and stops there, so a
tracked row that rejects material had nothing to say where that material came
from. The desk offers no field for it either, and the receipt could not be
submitted: the entry for the in-transit warehouse was handed the package of the
accepted warehouse.

The row takes a package of its own for the rejected material now, built from what
the delivery note put in the in-transit warehouse. Moving the package of a row
between the two warehouses also reads that delivery note package, instead of the
package it happens to hold, which no longer covers the qty once the split changes.

* test(stock): cover the package built for rejected batch material

* fix(stock): empty the in-transit warehouse when every unit is rejected

A receipt that rejects the whole qty left the material in the in-transit
warehouse and added it to the rejected warehouse as well, because the entries of
a row were made only when there was an accepted qty. They are made from the qty
that leaves the source warehouse now, so a row with no accepted qty is posted
like any other. One gate replaces two nested ones, which moves the body of the
loop out by a level; read the diff with whitespace ignored.

Such a row also carried no valuation rate, since the rate of an internal transfer
is taken from the accepted qty alone, and the rejected warehouse was then debited
without a matching credit. The rate falls back to the rejected qty.

Returning that material from the rejected warehouse left the in-transit warehouse
holding the qty at no value and wrote the value off: the return has no delivery
note reference, so its entry for that warehouse got no rate, and the entry
against it was suppressed because rejected material normally carries none. It
takes the rate of the return now, and the value of the source warehouse is signed
rather than absolute, so a return debits the warehouse the material returns to.

* test(stock): cover an internal transfer with every unit rejected

* fix(stock): keep resizing the package of a row whose qty changed

The package of rejected material was built between the two branches that build
and resize the package of a row, which left the resize attached to it. A row
whose qty changed after its package was built stopped being resized, and the
receipt was refused for the qty it no longer had.

* test(stock): state the rejected valuation setting the transfer test relies on

* fix(stock): keep a charge off the rejected material of a transfer

A landed cost voucher rebuilds the receipt with the charge spread over the
material it accepted, and the package of rejected material was then valued at
that same rate. Three units rejected out of a transfer worth 100 each came to
351.43 after a charge of 120, and the difference was credited to Cost of Goods
Sold to make the entries balance.

Rejected material of a transfer keeps the value it arrived in transit with. The
share of the charge that would have sat on it is expensed instead.

* test(stock): cover a charge on a transfer that rejected material
2026-09-22 20:32:12 +05:30
Mihir Kandoi
726c92f8ae fix(stock): let rejected serial and batch material leave the in-transit warehouse (#59252)
* fix(stock): let rejected serial and batch material leave the in-transit warehouse

A serial or batch item rejected on an internal transfer could not be received at
all. The package for the in-transit warehouse is copied from the delivery note,
and the copy was never resized, because the check compared a positive qty against
the negative total of an outgoing package.

The package of a row follows the split now. A row that rejects material carries
the package of its accepted warehouse, holding the accepted material alone,
which is the entry it belongs to and the total the desk sets its accepted qty
from. A row that rejects nothing keeps the package of the in-transit warehouse it
came out of. Editing the split moves the package from one to the other, and a row
that accepts nothing carries no package at all.

The entry for the in-transit warehouse gets a package of its own, holding the
accepted and the rejected material together. A landed cost voucher or a repost
reuses it rather than building a second one, which would make the batch qty count
the material twice.

Rejected material of an internal transfer keeps its rate, since its value was
credited out of the in-transit warehouse; refusing it a rate left the difference
to be written off. Cancelling reverses that warehouse with the package its entry
posted, after the rejected warehouse, so serial numbers are not put back and
taken out again. A return builds an inward package covering both.

The resize also fixes an ordinary partial receipt of a tracked item bought in
another UOM: the package is sized in stock UOM, which is what the row is
validated against.

* test(stock): cover rejected serial and batch material on an internal transfer

* fix(stock): build the package of rejected material on an internal transfer

A receipt of an internal transfer builds one package and stops there, so a
tracked row that rejects material had nothing to say where that material came
from. The desk offers no field for it either, and the receipt could not be
submitted: the entry for the in-transit warehouse was handed the package of the
accepted warehouse.

The row takes a package of its own for the rejected material now, built from what
the delivery note put in the in-transit warehouse. Moving the package of a row
between the two warehouses also reads that delivery note package, instead of the
package it happens to hold, which no longer covers the qty once the split changes.

* test(stock): cover the package built for rejected batch material

* fix(stock): keep resizing the package of a row whose qty changed

The package of rejected material was built between the two branches that build
and resize the package of a row, which left the resize attached to it. A row
whose qty changed after its package was built stopped being resized, and the
receipt was refused for the qty it no longer had.

* fix(stock): keep a charge off the rejected material of a transfer

A landed cost voucher rebuilds the receipt with the charge spread over the
material it accepted, and the package of rejected material was then valued at
that same rate. Three units rejected out of a transfer worth 100 each came to
351.43 after a charge of 120, and the difference was credited to Cost of Goods
Sold to make the entries balance.

Rejected material of a transfer keeps the value it arrived in transit with. The
share of the charge that would have sat on it is expensed instead.

* test(stock): cover a charge on a transfer that rejected material
2026-09-22 20:32:11 +05:30
Mihir Kandoi
c0f13b01de fix(stock): deduct rejected qty from the in-transit warehouse (#59251)
* fix(stock): deduct rejected qty from the in-transit warehouse

On an internal transfer the receipt took only the accepted qty out of the
in-transit warehouse, while the rejected qty was booked into the rejected
warehouse, so the rejected material was counted in both.

It also carried stock value, because an internal transfer anchors every inward
entry to the rate of the delivery note, but the rejected warehouse got no
accounting entry unless Buying Settings asked for one. The stock value and the
account value then disagreed.

The entry for the in-transit warehouse covers the accepted and the rejected qty
now, and the rejected warehouse is booked whatever that setting says, since the
value came out of the in-transit warehouse either way.

* test(stock): cover rejected qty on an internal transfer receipt

* test(stock): state the rejected valuation setting the transfer test relies on
2026-09-22 20:32:11 +05:30
rohitwaghchaure
23afc786bb feat: use serial no wise valuation switch on item (backport #59082) (#59182)
feat: use serial no wise valuation switch on item (#59082)
2026-09-22 14:00:42 +00:00
Mihir Kandoi
3bf3bc709f fix(stock): carry the batch of a delivery into the in-transit warehouse (#59264) 2026-09-22 18:17:14 +05:30
Pandiyan P
102842253b fix(stock): use net purchase values in landed cost vouchers (#59274)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-09-22 18:05:16 +05:30
Sudharsanan Ashok
e814d13126 fix(accounts): keep price list within user permissions (#59231)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-09-22 18:04:26 +05:30
Sudharsanan Ashok
7f608f31eb fix(stock): keep mapped cost center in stock entry items (#59286) 2026-09-22 18:02:53 +05:30
Sudharsanan Ashok
f990df9deb refactor(selling): remove ensure delivery based on produced serial no (#59217)
* refactor(selling): remove ensure delivery based on produced serial no

the ensure_delivery_based_on_produced_serial_no checkbox on sales order
item only ever validated itself at sales order save: serialized item,
active bom, and the same setting on every row of an item. nothing
downstream read the flag, so a delivery note or stock updating sales
invoice could ship any serial no and submit silently. the field promised
a guarantee it never enforced.

remove the field, its typing entry and validate_serial_no_based_delivery.
stock reservation on the sales order and work order is the supported way
to hold produced stock for an order. the database column is left in place
by migrate, so no data is dropped.

* chore(stock): remove dead reserved serial and batch no validators

validate_reserved_serial_nos and validate_reserved_batch_nos lost their
only callers in a20951e1cd ("fix: reserved serial nos validation"), which
moved serial level reservation checks into serial and batch bundle but
left both functions in stock ledger. drop them along with the three
imports that only they used.
2026-09-22 17:54:54 +05:30
Sudharsanan Ashok
62cb38d36b fix(stock): seed bin values when cancelling a stock voucher (#59250)
* fix(stock): seed bin values when cancelling a stock voucher

cancellation flags every sle of the voucher before update_entries_after
runs, so get_sle_against_current_voucher returns nothing and the seeding
added in #57380 never fires. prev_sle_dict stays empty, update_bin()
writes nothing, and the bin keeps the stock value and valuation rate it
had before the cancellation while its quantity is restored.

seed from the args when the query comes back empty, leaving the existing
anchor in place whenever a live entry shares the posting datetime.

* test(stock): cover bin stock value after cancelling a transfer

a transfer between two warehouses that both hold stock, then cancelled:
both bins must return to their previous quantity, valuation rate and
stock value. fails on develop with 500.0 != 1000.
2026-09-22 17:53:07 +05:30
Mihir Kandoi
8d6ee86ba3 fix: don't bump Customer timestamp when caching primary address (#59285)
Saving a Customer runs create_primary_address, which calls frappe.set_value on the
linked Address to ensure is_primary_address. That is a full document save, so
ERPNextAddress.on_update fires and writes the address display back to
Customer.primary_address with update_modified=True. The Customer row's modified
moves after the document has already been written, so the form keeps the older
timestamp and the next save from the same form fails check_if_latest with
TimestampMismatchError.

primary_address is a denormalized display cache, not a user edit, so writing it
must not move the optimistic lock timestamp.
2026-09-22 12:10:21 +00:00
Khushi Rawat
e3fc1790d4 Merge pull request #59262 from aerele/fix-reverse-je-mandatory-fields
fix(journal-entry): carry reference fields into reverse journal entry
2026-09-22 17:15:30 +05:30
rohitwaghchaure
719b53f2ad fix: do not zero out backdated stock at a stock reco adjustment entry (#59269)
* fix: do not zero out backdated stock at a stock reco adjustment entry

* fix: keep a stock reco adjustment entry value-only on cancel and refresh

* fix: read stock reco adjustment rows once and value them from the ledger
2026-09-22 16:38:51 +05:30
Pandiyan P
79fd06a1ba fix(crm): keep opportunity type default untranslated (#59277) 2026-09-22 11:06:45 +00:00
Khushi Rawat
5ebee655b7 Merge pull request #59180 from khushi8112/pos-price-list-exchange-rate
fix: refresh price list exchange rate when currency changes
2026-09-22 15:29:36 +05:30
Mihir Kandoi
9f2b05c103 fix: skip serial nos with no stock when reconciling specific serial nos (#59266) 2026-09-22 14:31:14 +05:30
ervishnucs
9e01992217 fix(journal-entry): carry reference fields into reverse journal entry 2026-09-22 13:25:11 +05:30
Shllokkk
5ebc52c95c Merge pull request #59240 from Shllokkk/asset-depr-schedule-company-filter
fix: asset field unfilterable on manual Asset Depreciation Schedule
2026-09-22 12:36:50 +05:30
ruthra kumar
86eb9c6204 Merge pull request #59230 from ruthra-kumar/more_changes_to_pcv_using_mapreduce
refactor(pcv): status on list view, progress update on UI
2026-09-22 12:19:05 +05:30
Afsal Syed
893fc75df4 fix(stock): apply transit warehouse filter to target warehouse in items table (#59192) 2026-09-22 06:48:07 +00:00
ruthra kumar
f2279a139f refactor(pcv): status on list view, progress update on UI 2026-09-22 11:50:28 +05:30
Mihir Kandoi
30e0382aa9 feat(selling): group Sales Order Analysis by item (#59236) 2026-09-22 11:09:20 +05:30
Mihir Kandoi
d2b1965cc8 fix(stock): compare transit quantities in stock UOM (#59202) 2026-09-22 11:08:09 +05:30
Shllokkk
a931c7d2f4 fix: remove company link filter on asset field in Asset Depreciation Schedule 2026-09-22 03:36:40 +05:30
Nikhil Kothari
3c251254b8 feat: update icons and documentation links for DocTypes and reports (#59235)
* feat: update icons and documentation links for DocTypes

* feat: add documentation links in ERPNext

* fix: migrate to newer Lucide icons
2026-09-21 12:32:05 +00:00
Shllokkk
04c949a662 fix: use correct exchange rate for multi-currency bank reconciliation payments (#59144)
* fix: use correct exchange rate for multi-currency bank reconciliation payments

* test: exchange rate for multi-currency bank reconciliation payment
2026-09-21 17:30:25 +05:30
Nishka Gosalia
5d00fec1c2 Merge pull request #59177 from nishkagosalia/stock-unreserve-issue
fix: unreserving stock failing in work order
2026-09-21 14:20:18 +05:30
Raffael Meyer
bb16dca4f7 ci: bump po-review-action to v1.2.0 (#59224) 2026-09-21 08:19:34 +00:00
Shllokkk
79fdc8add3 Merge pull request #59198 from Shllokkk/gp-returned-invoices-company-filter
perf: filter by company in get_returned_invoice_items
2026-09-21 13:43:30 +05:30
Sudharsanan Ashok
6e87694bbe fix(stock): honour pick manually when mapping a pick list (#59220) 2026-09-21 13:32:58 +05:30
rohitwaghchaure
6eda8c8c62 fix: write off stranded stock value only when the warehouse is empty (#59218) 2026-09-21 13:29:57 +05:30
Khushi Rawat
fb69724cda Merge pull request #59128 from aerele/fix/coupon-code-validation
fix: validate coupon pricing rules and validity dates
2026-09-21 12:46:57 +05:30
Mihir Kandoi
dd72c2688b chore(desk): hide CRM and Support on the dock (#59208) 2026-09-21 04:03:46 +00:00
MochaMind
8fd0175b06 chore: update POT file (#59201) 2026-09-20 21:44:23 +02:00
Shllokkk
1e35af3abe perf: filter by company in get_returned_invoice_items 2026-09-20 01:33:39 +05:30
khushi8112
221baabf9e test: cover price list exchange rate currency changes 2026-09-18 17:47:34 +05:30
khushi8112
8b59d66f94 fix: refresh price list exchange rate when currency changes 2026-09-18 17:34:55 +05:30
nishkagosalia
ff70aa6a33 fix: unreserving stock failing in work order 2026-09-18 17:12:01 +05:30
pandiyan
3384c1939b test: cover coupon pricing rule and date validation 2026-09-17 11:54:06 +05:30
pandiyan
14275e4c01 fix: validate coupon pricing rules and validity dates 2026-09-17 11:31:59 +05:30
382 changed files with 6338 additions and 2384 deletions

View File

@@ -22,6 +22,6 @@ jobs:
pull-requests: write
steps:
- uses: alyf-de/po-review-action@5928f84d6bc9094f9ad6e2c5780f01c0044b800e # v1.1.1
- uses: alyf-de/po-review-action@57fff275f4a0518a2ca55869ec6776fa3813b3d5 # v1.2.0
with:
hidden-po-files: eo.po

View File

@@ -47,10 +47,13 @@ class ERPNextAddress(Address):
super().on_update()
address_display = get_address_display(self.as_dict())
filters = {"customer_primary_address": self.name}
customers = frappe.db.get_all("Customer", filters=filters, as_list=True)
for customer_name in customers:
frappe.db.set_value("Customer", customer_name[0], "primary_address", address_display)
customers = frappe.db.get_all(
"Customer", filters={"customer_primary_address": self.name}, pluck="name"
)
for customer in customers:
frappe.db.set_value(
"Customer", customer, "primary_address", address_display, update_modified=False
)
@frappe.whitelist()

View File

@@ -7,6 +7,7 @@
"description": "Heads (or groups) against which Accounting Entries are made and balances are maintained.",
"doctype": "DocType",
"document_type": "Setup",
"documentation": "https://docs.frappe.io/erpnext/chart-of-accounts",
"engine": "InnoDB",
"field_order": [
"properties",
@@ -200,7 +201,7 @@
"options": "Account Category"
}
],
"icon": "fa fa-money",
"icon": "vault",
"idx": 1,
"is_tree": 1,
"links": [],

View File

@@ -40,6 +40,7 @@
}
],
"grid_page_length": 50,
"icon": "folder-tree",
"index_web_pages_for_search": 1,
"links": [
{

View File

@@ -148,7 +148,7 @@
"precision": "9"
}
],
"icon": "fa fa-list",
"icon": "scale",
"in_create": 1,
"links": [],
"modified": "2025-08-22 19:13:50.400404",

View File

@@ -3,6 +3,7 @@
"autoname": "field:label",
"creation": "2019-05-04 18:13:37.002352",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/accounting-dimensions",
"engine": "InnoDB",
"field_order": [
"document_type",
@@ -49,6 +50,7 @@
"options": "Accounting Dimension Detail"
}
],
"icon": "layers",
"links": [],
"modified": "2024-03-27 13:05:56.890002",
"modified_by": "Administrator",

View File

@@ -3,6 +3,7 @@
"autoname": "format:{accounting_dimension}-{#####}",
"creation": "2020-11-08 18:28:11.906146",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/accounting-dimension-filter",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -99,6 +100,7 @@
"label": "Fieldname"
}
],
"icon": "funnel",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-08-08 14:13:22.203011",

View File

@@ -3,6 +3,7 @@
"autoname": "field:period_name",
"creation": "2018-04-13 18:50:14.672323",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/accounting-period",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -77,6 +78,7 @@
"options": "Role"
}
],
"icon": "calendar-range",
"links": [],
"modified": "2026-03-09 17:15:33.577217",
"modified_by": "Administrator",

View File

@@ -3,6 +3,7 @@
"creation": "2013-06-24 15:49:57",
"doctype": "DocType",
"document_type": "Other",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/accounts-settings",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -801,7 +802,7 @@
}
],
"grid_page_length": 50,
"icon": "icon-cog",
"icon": "settings",
"idx": 1,
"index_web_pages_for_search": 1,
"issingle": 1,

View File

@@ -4,6 +4,7 @@
"creation": "2024-10-16 16:57:12.085072",
"doctype": "DocType",
"engine": "InnoDB",
"icon": "book-open",
"is_submittable": 1,
"field_order": [
"company",

View File

@@ -6,6 +6,7 @@
"creation": "2018-04-07 16:59:59.496668",
"doctype": "DocType",
"document_type": "Setup",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/bank",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -100,6 +101,7 @@
"read_only": 1
}
],
"icon": "landmark",
"links": [],
"modified": "2026-08-21 23:11:39.423431",
"modified_by": "Administrator",

View File

@@ -5,6 +5,7 @@
"creation": "2017-05-29 21:35:13.136357",
"doctype": "DocType",
"document_type": "Setup",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/bank-account",
"engine": "InnoDB",
"field_order": [
"account_name",
@@ -227,6 +228,7 @@
}
],
"grid_page_length": 50,
"icon": "credit-card",
"links": [
{
"group": "Transactions",

View File

@@ -44,6 +44,7 @@
}
],
"grid_page_length": 50,
"icon": "wallet",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2026-06-16 22:17:48.007982",

View File

@@ -18,6 +18,7 @@
"unique": 1
}
],
"icon": "credit-card",
"links": [],
"modified": "2024-03-27 13:06:37.221876",
"modified_by": "Administrator",

View File

@@ -18,6 +18,7 @@
"unique": 1
}
],
"icon": "credit-card",
"links": [],
"modified": "2024-03-27 13:06:37.347035",
"modified_by": "Administrator",

View File

@@ -87,7 +87,7 @@
}
],
"hide_toolbar": 1,
"icon": "fa fa-check",
"icon": "badge-check",
"idx": 1,
"issingle": 1,
"links": [],

View File

@@ -5,6 +5,7 @@
"creation": "2016-12-17 10:43:35.731631",
"doctype": "DocType",
"document_type": "Document",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/bank-guarantee",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -216,6 +217,7 @@
}
],
"grid_page_length": 50,
"icon": "shield-check",
"is_submittable": 1,
"links": [],
"modified": "2026-05-25 18:12:10.768835",

View File

@@ -116,6 +116,7 @@
}
],
"hide_toolbar": 1,
"icon": "arrow-left-right",
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],

View File

@@ -792,6 +792,7 @@ def create_bulk_payment_entry_and_reconcile(
"deposit",
"withdrawal",
"bank_account",
"company",
"currency",
"unallocated_amount",
"date",
@@ -826,11 +827,7 @@ def create_bulk_payment_entry_and_reconcile(
"paid_from": paid_from,
"paid_to": paid_to,
"paid_amount": bank_transaction.unallocated_amount,
"base_paid_amount": bank_transaction.unallocated_amount,
"received_amount": bank_transaction.unallocated_amount,
"base_received_amount": bank_transaction.unallocated_amount,
"target_exchange_rate": 1,
"source_exchange_rate": 1,
"reference_date": bank_transaction.date,
"posting_date": bank_transaction.date,
"reference_no": (bank_transaction.reference_number or bank_transaction.description or "")[
@@ -839,6 +836,8 @@ def create_bulk_payment_entry_and_reconcile(
}
)
set_multi_currency_amounts(payment_entry_doc)
payment_entry_doc.insert()
payment_entry_doc.submit()
@@ -877,6 +876,7 @@ def create_payment_entry_and_reconcile(bank_transaction_name: str | int, payment
"doctype": "Payment Entry",
}
)
set_multi_currency_amounts(payment_entry)
payment_entry.insert()
payment_entry.submit()
transaction = reconcile_vouchers(
@@ -899,6 +899,33 @@ def create_payment_entry_and_reconcile(bank_transaction_name: str | int, payment
}
def set_multi_currency_amounts(pe):
"""Set real exchange rates when the bank and party accounts differ in currency."""
company_currency = frappe.get_cached_value("Company", pe.company, "default_currency")
pe.paid_from_account_currency = frappe.get_cached_value("Account", pe.paid_from, "account_currency")
pe.paid_to_account_currency = frappe.get_cached_value("Account", pe.paid_to, "account_currency")
pe.source_exchange_rate = (
1.0
if pe.paid_from_account_currency == company_currency
else get_exchange_rate(pe.paid_from_account_currency, company_currency, pe.posting_date)
)
pe.target_exchange_rate = (
1.0
if pe.paid_to_account_currency == company_currency
else get_exchange_rate(pe.paid_to_account_currency, company_currency, pe.posting_date)
)
# derive the party-side amount from the authoritative bank-side amount; Payment Entry books any
# rounding residual to Exchange Gain/Loss during validation (set_exchange_gain_loss)
if pe.payment_type == "Receive" and pe.source_exchange_rate:
base_amount = flt(pe.received_amount) * pe.target_exchange_rate
pe.paid_amount = flt(base_amount / pe.source_exchange_rate, pe.precision("paid_amount"))
elif pe.payment_type == "Pay" and pe.target_exchange_rate:
base_amount = flt(pe.paid_amount) * pe.source_exchange_rate
pe.received_amount = flt(base_amount / pe.target_exchange_rate, pe.precision("received_amount"))
@frappe.whitelist(methods=["GET"])
def search_for_transfer_transaction(transaction_id: str | int):
"""

View File

@@ -2,12 +2,16 @@
# See license.txt
from unittest.mock import patch
import frappe
from frappe import qb
from frappe.utils import add_days, today
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
auto_reconcile_vouchers,
create_bulk_payment_entry_and_reconcile,
create_payment_entry_and_reconcile,
get_auto_reconcile_message,
get_bank_transactions,
get_linked_payments,
@@ -16,6 +20,8 @@ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_pay
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.tests.utils import ERPNextTestSuite
RATE_METHOD = "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_exchange_rate"
class TestBankReconciliationTool(ERPNextTestSuite, AccountsTestMixin):
def setUp(self):
@@ -230,3 +236,117 @@ class TestBankReconciliationTool(ERPNextTestSuite, AccountsTestMixin):
self.assertIn("1 Transaction Partially Reconciled", singular)
plural, _ = get_auto_reconcile_message(["p1", "p2"], [])
self.assertIn("2 Transactions Partially Reconciled", plural)
def test_multi_currency_pay_converts_and_balances(self):
# withdrawal from an INR bank paying a USD supplier; rate 3.0 makes 100/3 non-exact
self.enable_multi_currency_setup()
pe = self.reconcile_new_payment(
self.make_multi_currency_txn(withdrawal=100),
payment_type="Pay",
party_type="Supplier",
party=self.supplier,
party_account=self.creditors_usd,
paid_from=self.bank,
paid_to=self.creditors_usd,
rate=3.0,
)
self.assertEqual(pe.docstatus, 1) # submits despite the rounding residual
self.assertEqual((pe.source_exchange_rate, pe.target_exchange_rate), (1.0, 3.0))
self.assertEqual((pe.paid_amount, pe.received_amount), (100, 33.33)) # bank side kept, 100/3
self.assertEqual(pe.difference_amount, 0)
# Payment Entry auto-books the rounding residual to Exchange Gain/Loss
self.assertTrue(pe.deductions[0].is_exchange_gain_loss)
self.assertEqual(pe.deductions[0].amount, 0.01) # 100 - 33.33 * 3
def test_multi_currency_receive_converts_and_balances(self):
# deposit into an INR bank from a USD customer; the party side must convert
self.enable_multi_currency_setup()
pe = self.reconcile_new_payment(
self.make_multi_currency_txn(deposit=100),
payment_type="Receive",
party_type="Customer",
party=self.customer,
party_account=self.debtors_usd,
paid_from=self.debtors_usd,
paid_to=self.bank,
rate=3.0,
)
self.assertEqual(pe.docstatus, 1)
self.assertEqual((pe.source_exchange_rate, pe.target_exchange_rate), (3.0, 1.0))
self.assertEqual((pe.received_amount, pe.paid_amount), (100, 33.33)) # bank side kept, 100/3
self.assertEqual(pe.difference_amount, 0)
def test_multi_currency_bulk_pay_converts_and_balances(self):
# the bulk path builds the Payment Entry itself, so it must convert too
self.enable_multi_currency_setup()
txn = self.make_multi_currency_txn(withdrawal=100)
with patch(RATE_METHOD, return_value=3.0):
result = create_bulk_payment_entry_and_reconcile(
[txn.name], "Supplier", self.supplier, self.creditors_usd
)
pe = frappe.get_doc("Payment Entry", result[0]["payment_entry"].name)
self.assertEqual(pe.docstatus, 1)
self.assertEqual(pe.target_exchange_rate, 3.0)
self.assertEqual((pe.paid_amount, pe.received_amount), (100, 33.33))
self.assertEqual(pe.difference_amount, 0)
def enable_multi_currency_setup(self):
# USD party/accounts + a company gain/loss account to absorb rounding residuals
self.company_abbr = "_TC"
self.create_supplier(supplier_name="_Test Supplier USD", currency="USD")
self.create_customer(customer_name="_Test Customer USD", currency="USD")
self.create_usd_payable_account()
self.create_usd_receivable_account()
self.set_party_account("Supplier", self.supplier, self.creditors_usd)
if not frappe.db.get_value("Company", self.company, "exchange_gain_loss_account"):
frappe.db.set_value(
"Company", self.company, "exchange_gain_loss_account", "Exchange Gain/Loss - _TC"
)
def set_party_account(self, party_type, party, account):
doc = frappe.get_doc(party_type, party)
if not any(row.company == self.company for row in doc.accounts):
doc.append("accounts", {"company": self.company, "account": account})
doc.save()
def make_multi_currency_txn(self, withdrawal=0, deposit=0):
return (
frappe.get_doc(
{
"doctype": "Bank Transaction",
"date": today(),
"withdrawal": withdrawal,
"deposit": deposit,
"bank_account": self.bank_account,
"currency": "INR",
"reference_number": "TEST-FX-REF",
}
)
.save()
.submit()
)
def reconcile_new_payment(
self, txn, *, payment_type, party_type, party, party_account, paid_from, paid_to, rate
):
# mimics the /banking frontend, which sends a hardcoded 1:1 rate
payment_entry_doc = {
"payment_type": payment_type,
"company": self.company,
"party_type": party_type,
"party": party,
"party_account": party_account,
"paid_from": paid_from,
"paid_to": paid_to,
"paid_amount": txn.unallocated_amount,
"received_amount": txn.unallocated_amount,
"source_exchange_rate": 1,
"target_exchange_rate": 1,
"posting_date": today(),
"reference_no": f"TEST-FX-{payment_type}",
"reference_date": today(),
}
with patch(RATE_METHOD, return_value=rate):
result = create_payment_entry_and_reconcile(txn.name, payment_entry_doc)
return frappe.get_doc("Payment Entry", result["payment_entry"].name)

View File

@@ -225,6 +225,7 @@
}
],
"hide_toolbar": 1,
"icon": "file-down",
"links": [],
"modified": "2026-06-19 14:18:00.000000",
"modified_by": "Administrator",

View File

@@ -188,6 +188,7 @@
}
],
"grid_page_length": 50,
"icon": "file-clock",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2026-07-09 17:55:25.615942",

View File

@@ -4,6 +4,7 @@
"autoname": "naming_series:",
"creation": "2018-10-22 18:19:02.784533",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/bank-transaction",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -277,6 +278,7 @@
}
],
"grid_page_length": 50,
"icon": "arrow-left-right",
"is_submittable": 1,
"links": [],
"modified": "2026-04-11 20:41:15.124085",

View File

@@ -140,6 +140,7 @@
}
],
"grid_page_length": 50,
"icon": "zap",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2026-04-11 21:36:26.754667",

View File

@@ -167,6 +167,7 @@
}
],
"hide_toolbar": 1,
"icon": "split",
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],

View File

@@ -68,6 +68,7 @@
"label": "Generated"
}
],
"icon": "git-branch",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-03-27 13:06:39.766063",

View File

@@ -4,6 +4,7 @@
"autoname": "naming_series:",
"creation": "2016-05-16 11:42:29.632528",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/budgeting",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -330,6 +331,7 @@
}
],
"grid_page_length": 50,
"icon": "piggy-bank",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],

View File

@@ -122,6 +122,7 @@
"read_only": 1
}
],
"icon": "lock",
"is_submittable": 1,
"links": [],
"modified": "2024-03-27 13:06:44.260440",

View File

@@ -5,6 +5,7 @@
"description": "Import Chart of Accounts from a csv file",
"doctype": "DocType",
"document_type": "Other",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/chart-of-accounts-importer",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -47,6 +48,7 @@
}
],
"hide_toolbar": 1,
"icon": "file-input",
"in_create": 1,
"issingle": 1,
"links": [],

View File

@@ -4,6 +4,7 @@
"autoname": "field:bank_name",
"creation": "2016-05-04 14:35:00.402544",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/cheque-print-template",
"engine": "InnoDB",
"field_order": [
"settings",
@@ -293,6 +294,7 @@
"fieldtype": "HTML"
}
],
"icon": "printer",
"links": [],
"max_attachments": 1,
"modified": "2026-06-08 12:10:35.829531",

View File

@@ -7,6 +7,7 @@
"description": "Track separate Income and Expense for product verticals or divisions.",
"doctype": "DocType",
"document_type": "Setup",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/cost-center",
"engine": "InnoDB",
"field_order": [
"sb0",
@@ -122,7 +123,7 @@
"label": "Disabled"
}
],
"icon": "fa fa-money",
"icon": "chart-pie",
"idx": 1,
"is_tree": 1,
"links": [],

View File

@@ -4,6 +4,7 @@
"autoname": "CC-ALLOC-.#####",
"creation": "2022-01-13 20:07:29.871109",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/cost_center_allocation",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -65,6 +66,7 @@
"read_only": 1
}
],
"icon": "chart-pie",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],

View File

@@ -5,7 +5,10 @@ frappe.ui.form.on("Coupon Code", {
setup: function (frm) {
frm.set_query("pricing_rule", function () {
return {
filters: [["Pricing Rule", "coupon_code_based", "=", "1"]],
filters: {
coupon_code_based: 1,
disable: 0,
},
};
});
},

View File

@@ -5,6 +5,7 @@
"creation": "2018-01-22 14:34:39.701832",
"doctype": "DocType",
"document_type": "Other",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/coupon-code",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -124,6 +125,7 @@
"label": "From External Ecomm Platform"
}
],
"icon": "ticket-percent",
"links": [],
"modified": "2026-08-21 23:11:41.010871",
"modified_by": "Administrator",

View File

@@ -42,7 +42,23 @@ class CouponCode(Document):
self.coupon_code = frappe.generate_hash()[:10].upper()
def validate(self):
self.validate_from_to_dates("valid_from", "valid_upto")
self.validate_pricing_rule()
if self.coupon_type == "Gift Card":
self.maximum_use = 1
if not self.customer:
frappe.throw(_("Please select the customer."))
def validate_pricing_rule(self):
if not self.pricing_rule or self.from_external_ecomm_platform:
return
# Allow existing coupons to be updated after their pricing rule is disabled.
if not (
self.has_value_changed("pricing_rule") or self.has_value_changed("from_external_ecomm_platform")
):
return
if frappe.db.get_value("Pricing Rule", self.pricing_rule, "disable"):
frappe.throw(_("Pricing Rule {0} is disabled").format(frappe.bold(self.pricing_rule)))

View File

@@ -112,6 +112,43 @@ class TestCouponCode(ERPNextTestSuite):
def setUp(self):
test_create_test_data()
def test_disabled_pricing_rule_validation(self):
coupon = frappe.get_doc("Coupon Code", "SAVE30")
rule = frappe.get_doc("Pricing Rule", coupon.pricing_rule)
rule.disable = 1
rule.save()
with self.subTest("new coupon cannot select a disabled rule"):
new_coupon = frappe.copy_doc(coupon)
new_coupon.coupon_name = "Festival Savings"
new_coupon.coupon_code = "FESTSAVE"
with self.assertRaisesRegex(frappe.ValidationError, "is disabled"):
new_coupon.insert()
with self.subTest("existing coupon can retain a disabled rule"):
coupon.description = "Offer paused"
coupon.save()
coupon.reload()
self.assertEqual(coupon.description, "Offer paused")
self.assertEqual(coupon.pricing_rule, rule.name)
with self.subTest("existing coupon cannot switch to a disabled rule"):
disabled_rule = frappe.copy_doc(rule)
disabled_rule.insert()
coupon.reload()
coupon.pricing_rule = disabled_rule.name
with self.assertRaisesRegex(frappe.ValidationError, "is disabled"):
coupon.save()
coupon.reload()
self.assertEqual(coupon.pricing_rule, rule.name)
def test_cannot_save_coupon_with_reversed_validity_dates(self):
coupon = frappe.get_doc("Coupon Code", "SAVE30")
coupon.valid_from = "2026-09-17"
coupon.valid_upto = "2026-09-02"
with self.assertRaises(frappe.exceptions.InvalidDates):
coupon.save()
def test_sales_order_with_coupon_code(self):
frappe.db.set_value("Coupon Code", "SAVE30", "used", 0)

View File

@@ -2,6 +2,7 @@
"actions": [],
"creation": "2022-01-10 13:03:26.237081",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/currency-exchange-settings",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -101,6 +102,7 @@
"label": "Use HTTP Protocol"
}
],
"icon": "refresh-cw",
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],

View File

@@ -5,6 +5,7 @@
"autoname": "naming_series:",
"creation": "2019-07-05 16:34:31.013238",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/dunning",
"engine": "InnoDB",
"field_order": [
"naming_series",
@@ -398,6 +399,7 @@
"fieldtype": "Column Break"
}
],
"icon": "bell-ring",
"is_submittable": 1,
"links": [],
"modified": "2026-05-30 23:18:04.712528",

View File

@@ -101,6 +101,7 @@
"fieldtype": "Column Break"
}
],
"icon": "bell",
"links": [
{
"link_doctype": "Dunning",

View File

@@ -4,6 +4,7 @@
"autoname": "ACC-ERR-.YYYY.-.#####",
"creation": "2018-04-13 18:25:55.943587",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/exchange-rate-revaluation",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -107,6 +108,7 @@
"precision": "9"
}
],
"icon": "arrow-right-left",
"is_submittable": 1,
"links": [],
"modified": "2024-03-27 13:09:42.951164",

View File

@@ -5,6 +5,7 @@
"creation": "2018-04-13 17:42:43.252224",
"doctype": "DocType",
"document_type": "Document",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/finance-book",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -18,7 +19,7 @@
"unique": 1
}
],
"icon": "fa fa-book",
"icon": "book",
"links": [],
"modified": "2026-08-21 23:11:42.386104",
"modified_by": "Administrator",

View File

@@ -3,6 +3,7 @@
"autoname": "field:template_name",
"creation": "2025-08-02 04:44:15.184541",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/financial-report-template",
"engine": "InnoDB",
"field_order": [
"template_name",
@@ -64,6 +65,7 @@
}
],
"grid_page_length": 50,
"icon": "file-spreadsheet",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2026-02-23 01:04:05.797161",

View File

@@ -6,6 +6,7 @@
"description": "Represents a Financial Year. All accounting entries and other major transactions are tracked against the Fiscal Year.",
"doctype": "DocType",
"document_type": "Setup",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/fiscal-year",
"engine": "InnoDB",
"field_order": [
"year",
@@ -79,7 +80,7 @@
"set_only_once": 1
}
],
"icon": "fa fa-calendar",
"icon": "calendar",
"idx": 1,
"links": [],
"modified": "2026-08-21 23:11:42.509102",

View File

@@ -377,7 +377,7 @@
"precision": "9"
}
],
"icon": "fa fa-list",
"icon": "book-open",
"idx": 1,
"in_create": 1,
"links": [],

View File

@@ -5,6 +5,7 @@
"autoname": "ACC-INV-DISC-.YYYY.-.#####",
"creation": "2019-03-07 12:01:56.296952",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/invoice_discounting",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -169,6 +170,7 @@
"read_only": 1
}
],
"icon": "ticket-percent",
"is_submittable": 1,
"links": [],
"modified": "2026-09-09 17:04:59.512294",

View File

@@ -5,6 +5,7 @@
"creation": "2022-01-19 01:09:13.297137",
"doctype": "DocType",
"document_type": "Setup",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/item-tax-template",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -56,6 +57,7 @@
"fieldtype": "Section Break"
}
],
"icon": "circle-percent",
"links": [],
"modified": "2026-08-21 23:11:43.571355",
"modified_by": "Administrator",

View File

@@ -6,6 +6,7 @@
"creation": "2022-01-25 10:29:58.717206",
"doctype": "DocType",
"document_type": "Document",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/journal-entry",
"engine": "InnoDB",
"field_order": [
"entry_type_and_date",
@@ -661,7 +662,7 @@
"label": "Custom Remark"
}
],
"icon": "fa fa-file-text",
"icon": "notebook-pen",
"idx": 176,
"is_submittable": 1,
"links": [

View File

@@ -253,6 +253,10 @@ def make_reverse_journal_entry(source_name: str, target_doc: str | dict | Docume
def post_process(source, target) -> None:
target.reversal_of = source.name
target.naming_series = source.naming_series
if source.voucher_type == "Bank Entry":
target.cheque_no = source.cheque_no
target.cheque_date = source.cheque_date
doclist = get_mapped_doc(
"Journal Entry",

View File

@@ -4,6 +4,7 @@
"creation": "2020-04-09 01:32:51.332301",
"doctype": "DocType",
"document_type": "Document",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/journal-entry-template",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -85,6 +86,7 @@
"label": "Multi Currency"
}
],
"icon": "notebook-text",
"links": [],
"modified": "2024-03-27 13:09:58.814734",
"modified_by": "Administrator",

View File

@@ -40,6 +40,7 @@
"label": "General and Payment Ledger mismatch"
}
],
"icon": "heart-pulse",
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],

View File

@@ -57,6 +57,7 @@
}
],
"hide_toolbar": 1,
"icon": "activity",
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],

View File

@@ -92,6 +92,7 @@
}
],
"hide_toolbar": 1,
"icon": "merge",
"links": [],
"modified": "2024-03-27 13:10:02.198009",
"modified_by": "Administrator",

View File

@@ -97,6 +97,7 @@
"label": "Discretionary Reason"
}
],
"icon": "star",
"in_create": 1,
"links": [],
"modified": "2024-07-01 08:51:13.927009",

View File

@@ -3,6 +3,7 @@
"autoname": "field:loyalty_program_name",
"creation": "2018-01-23 06:23:05.731431",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/loyalty-program",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -153,6 +154,7 @@
"options": "Project"
}
],
"icon": "gift",
"links": [],
"modified": "2026-08-21 23:11:44.144864",
"modified_by": "Administrator",

View File

@@ -6,6 +6,7 @@
"creation": "2012-12-04 17:49:20",
"doctype": "DocType",
"document_type": "Setup",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/mode-of-payment",
"engine": "InnoDB",
"field_order": [
"mode_of_payment",
@@ -44,7 +45,7 @@
"label": "Enabled"
}
],
"icon": "fa fa-credit-card",
"icon": "wallet",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],

View File

@@ -43,7 +43,7 @@
"options": "Monthly Distribution Percentage"
}
],
"icon": "fa fa-bar-chart",
"icon": "chart-bar",
"idx": 1,
"links": [],
"modified": "2026-08-21 23:11:44.908490",

View File

@@ -4,6 +4,7 @@
"allow_copy": 1,
"creation": "2017-08-29 02:22:54.947711",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/opening-invoice-creation-tool",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -88,6 +89,7 @@
}
],
"hide_toolbar": 1,
"icon": "file-plus",
"issingle": 1,
"links": [],
"modified": "2026-05-30 23:18:48.691227",

View File

@@ -50,6 +50,7 @@
"options": "secondary_role"
}
],
"icon": "link",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-03-27 13:10:08.607170",

View File

@@ -5,6 +5,7 @@
"autoname": "naming_series:",
"creation": "2016-06-01 14:38:51.012597",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/payment-entry",
"engine": "InnoDB",
"field_order": [
"type_of_payment",
@@ -784,6 +785,7 @@
}
],
"grid_page_length": 50,
"icon": "banknote",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [

View File

@@ -84,6 +84,7 @@
"reqd": 1
}
],
"icon": "credit-card",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-07-14 16:49:55.210352",

View File

@@ -5,6 +5,7 @@
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"icon": "book-open",
"is_submittable": 1,
"field_order": [
"posting_date",

View File

@@ -3,6 +3,7 @@
"autoname": "naming_series:",
"creation": "2018-07-20 16:43:08.505978",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/payment-order",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -107,6 +108,7 @@
"label": "Account"
}
],
"icon": "send",
"is_submittable": 1,
"links": [],
"modified": "2024-03-27 13:10:10.186727",

View File

@@ -3,6 +3,7 @@
"allow_copy": 1,
"creation": "2014-07-09 12:04:51.681583",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/payment-reconciliation",
"engine": "InnoDB",
"field_order": [
"company",
@@ -235,7 +236,7 @@
}
],
"hide_toolbar": 1,
"icon": "icon-resize-horizontal",
"icon": "arrow-left-right",
"is_virtual": 1,
"issingle": 1,
"links": [],

View File

@@ -3,6 +3,7 @@
"autoname": "naming_series:",
"creation": "2015-12-15 22:23:24.745065",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/payment-request",
"engine": "InnoDB",
"field_order": [
"payment_request_type",
@@ -474,6 +475,7 @@
}
],
"grid_page_length": 50,
"icon": "hand-coins",
"in_create": 1,
"index_web_pages_for_search": 1,
"is_submittable": 1,

View File

@@ -5,6 +5,7 @@
"autoname": "field:payment_term_name",
"creation": "2017-08-10 15:24:54.876365",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/payment-terms",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -115,6 +116,7 @@
"fieldtype": "Column Break"
}
],
"icon": "calendar-clock",
"links": [],
"modified": "2026-08-21 23:11:45.693762",
"modified_by": "Administrator",

View File

@@ -5,6 +5,7 @@
"autoname": "field:template_name",
"creation": "2017-08-10 15:34:28.058054",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/payment-terms-template",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -34,6 +35,7 @@
"label": "Allocate Payment Based On Payment Terms"
}
],
"icon": "calendar-clock",
"links": [],
"modified": "2024-04-07 11:26:42.021585",
"modified_by": "Administrator",

View File

@@ -20,6 +20,7 @@
}
],
"grid_page_length": 50,
"icon": "anchor",
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],

View File

@@ -3,6 +3,7 @@
"autoname": "ACC-PCV-.YYYY.-.#####",
"creation": "2013-01-10 16:34:07",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/period-closing-voucher",
"engine": "InnoDB",
"field_order": [
"transaction_date",
@@ -110,7 +111,7 @@
"reqd": 1
}
],
"icon": "fa fa-file-text",
"icon": "folder-lock",
"idx": 1,
"is_submittable": 1,
"links": [],

View File

@@ -280,12 +280,13 @@ class PeriodClosingVoucher(AccountsController):
data = self.get_data_for_mapreduce()
mapreduce(
"erpnext.accounts.doctype.period_closing_voucher.period_closing_voucher.mapper",
"erpnext.accounts.doctype.period_closing_voucher.period_closing_voucher.reducer",
"erpnext.accounts.doctype.period_closing_voucher.period_closing_voucher.process_date_range",
"erpnext.accounts.doctype.period_closing_voucher.period_closing_voucher.aggregate_partial_result",
"erpnext.accounts.doctype.period_closing_voucher.period_closing_voucher.summarize_and_post_ledger",
data,
self.doctype,
self.name,
f"Closing FY {self.fiscal_year}",
)
def on_cancel(self):
@@ -330,8 +331,8 @@ class PeriodClosingVoucher(AccountsController):
def make_gl_entries(self):
if frappe.db.estimate_count("GL Entry") > 100_000:
frappe.enqueue(
process_gl_and_closing_entries,
frappe.enqueue_task(
method=process_gl_and_closing_entries,
doc=self,
timeout=1800,
)
@@ -834,7 +835,7 @@ def get_previous_closed_period_in_current_year(fiscal_year, company):
return prev_closed_period_end_date
def mapper(val):
def process_date_range(val):
start_date = val.from_date
end_date = val.to_date
pcv = val.pcv
@@ -881,7 +882,7 @@ def mapper(val):
return res
def reducer(final, partial_res):
def aggregate_partial_result(final, partial_res):
if final is None:
final = []

View File

@@ -0,0 +1,28 @@
// Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.listview_settings["Period Closing Voucher"] = {
add_fields: ["gle_processing_status"],
get_indicator: function (doc) {
const status_colors = {
Draft: "red",
Submitted: "blue",
Cancelled: "red",
};
const gle_processing_status = {
"In Progress": [__("Processing GL Entries"), "blue"],
Completed: [__("Period Closed"), "green"],
Failed: [__("Period Closing Failed"), "red"],
};
if (doc.docstatus != 0) {
return [
gle_processing_status[doc.gle_processing_status][0],
gle_processing_status[doc.gle_processing_status][1],
"gle_processing_status,=," + doc.gle_processing_status,
];
}
return [__(doc.docstatus), status_colors[doc.docstatus], "docstatus,=," + doc.docstatus];
},
};

View File

@@ -254,6 +254,7 @@
}
],
"grid_page_length": 50,
"icon": "book-lock",
"is_submittable": 1,
"links": [
{

View File

@@ -1640,7 +1640,7 @@
"print_hide": 1
}
],
"icon": "fa fa-file-text",
"icon": "receipt-text",
"is_submittable": 1,
"links": [],
"modified": "2026-08-21 23:11:45.029925",

View File

@@ -126,6 +126,7 @@
"reqd": 1
}
],
"icon": "merge",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],

View File

@@ -129,6 +129,7 @@
"reqd": 1
}
],
"icon": "folder-open",
"is_submittable": 1,
"links": [],
"modified": "2024-03-27 13:10:16.034296",

View File

@@ -4,6 +4,7 @@
"autoname": "Prompt",
"creation": "2013-05-24 12:15:51",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/pos-profile",
"engine": "InnoDB",
"field_order": [
"company",
@@ -557,7 +558,7 @@
}
],
"grid_page_length": 50,
"icon": "icon-cog",
"icon": "monitor",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [

View File

@@ -51,6 +51,7 @@
}
],
"hide_toolbar": 0,
"icon": "settings",
"issingle": 1,
"links": [],
"modified": "2026-03-16 13:28:19.677217",

View File

@@ -5,6 +5,7 @@
"autoname": "naming_series:",
"creation": "2014-02-21 15:02:51",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/pricing-rule",
"engine": "InnoDB",
"field_order": [
"applicability_section",
@@ -654,7 +655,7 @@
"label": "Don't Enforce Free Item Qty"
}
],
"icon": "fa fa-gift",
"icon": "badge-percent",
"idx": 1,
"links": [],
"modified": "2026-02-17 12:24:07.553505",

View File

@@ -3,6 +3,7 @@
"autoname": "ACC-PDA-.#####",
"creation": "2019-11-04 18:01:23.454775",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/process-deferred-accounting",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -74,6 +75,7 @@
"reqd": 1
}
],
"icon": "list-clock",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],

View File

@@ -154,6 +154,7 @@
"reqd": 0
}
],
"icon": "scale",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],

View File

@@ -106,6 +106,7 @@
"read_only": 1
}
],
"icon": "file-clock",
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],

View File

@@ -80,6 +80,7 @@
}
],
"grid_page_length": 50,
"icon": "lock",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],

View File

@@ -4,6 +4,7 @@
"autoname": "Prompt",
"creation": "2020-05-22 16:46:18.712954",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/process-statement-of-accounts",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -424,6 +425,7 @@
"label": "Show Opening Entries"
}
],
"icon": "mail",
"links": [],
"modified": "2026-06-01 15:37:07.660442",
"modified_by": "Administrator",

View File

@@ -35,6 +35,7 @@
"options": "Subscription"
}
],
"icon": "repeat",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],

View File

@@ -5,6 +5,7 @@
"autoname": "Prompt",
"creation": "2019-02-08 17:10:36.077402",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/promotional-scheme",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -279,6 +280,7 @@
"options": "Promotional Scheme Product Discount"
}
],
"icon": "megaphone",
"links": [],
"modified": "2025-08-20 11:48:23.231081",
"modified_by": "Administrator",

View File

@@ -6,6 +6,7 @@
"creation": "2013-05-21 16:16:39",
"doctype": "DocType",
"document_type": "Document",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/purchase-invoice",
"email_append_to": 1,
"engine": "InnoDB",
"field_order": [
@@ -1692,7 +1693,7 @@
}
],
"grid_page_length": 50,
"icon": "fa fa-file-text",
"icon": "receipt-text",
"idx": 204,
"is_submittable": 1,
"links": [],

View File

@@ -256,6 +256,10 @@ class PurchaseInvoiceGLComposer(BaseGLComposer):
)
else:
self.make_rejected_warehouse_gl_entry(
gl_entries, item, voucher_wise_stock_value, inventory_account_map
)
if not doc.is_internal_transfer():
gl_entries.append(
self.get_gl_dict(
@@ -572,12 +576,18 @@ class PurchaseInvoiceGLComposer(BaseGLComposer):
def make_rejected_warehouse_gl_entry(
self, gl_entries, item, voucher_wise_stock_value, inventory_account_map
) -> float:
"""Book the rejected material of an internal transfer, whose value was credited out of the
in-transit warehouse along with the accepted material."""
"""Book the material the invoice moved into the rejected warehouse.
An internal transfer carries the value credited out of the in-transit warehouse along with
the accepted material, so the entry against it is that warehouse, and the caller credits it
for both. On an ordinary invoice the supplier entry already holds the cost.
"""
doc = self.doc
if not (doc.is_internal_transfer() and flt(item.rejected_qty) and item.rejected_warehouse):
if not (item.rejected_warehouse and flt(item.rejected_qty)):
return 0.0
transfers_rejected_material = doc.is_internal_transfer()
rejected_amount = flt(
voucher_wise_stock_value.get((item.name, item.rejected_warehouse)),
item.precision("base_net_amount"),
@@ -585,23 +595,26 @@ class PurchaseInvoiceGLComposer(BaseGLComposer):
if not rejected_amount:
return 0.0
_inv_dict = doc.get_inventory_account_dict(item, inventory_account_map, "rejected_warehouse")
rejected_account = doc.get_inventory_account_dict(item, inventory_account_map, "rejected_warehouse")
gl_entries.append(
self.get_gl_dict(
{
"account": _inv_dict["account"],
"against": item.expense_account,
"account": rejected_account["account"],
"against": item.expense_account if transfers_rejected_material else doc.supplier,
"cost_center": item.cost_center,
"project": item.project or doc.project,
"remarks": doc.get("remarks") or _("Accounting Entry for Stock"),
"debit": rejected_amount,
"debit_in_transaction_currency": flt(
rejected_amount / doc.conversion_rate, item.precision("net_amount")
),
},
_inv_dict["account_currency"],
rejected_account["account_currency"],
item=item,
)
)
return rejected_amount
return rejected_amount if transfers_rejected_material else 0.0
def make_stock_adjustment_entry(self, gl_entries, item, voucher_wise_stock_value, account_currency):
doc = self.doc
@@ -616,16 +629,24 @@ class PurchaseInvoiceGLComposer(BaseGLComposer):
if doc.is_return and doc.update_stock and (doc.is_internal_supplier or not doc.return_against):
net_rate = item.base_net_amount
if item.sales_incoming_rate:
net_rate = item.qty * item.sales_incoming_rate
# Material of a transfer goes back at the rate it came in with, the rejected
# material along with the accepted.
net_rate = (flt(item.qty) + flt(item.rejected_qty)) * item.sales_incoming_rate
stock_amount = net_rate + item.item_tax_amount + flt(item.landed_cost_voucher_amount)
warehouse_debit_amount = flt(
voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision
)
if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision):
# The rejected warehouse carries the rest of what the invoice paid for, and is booked
# by its own entry, so it is not a variance.
returned_stock_value = warehouse_debit_amount + flt(
voucher_wise_stock_value.get((item.name, item.rejected_warehouse)), net_amt_precision
)
if flt(stock_amount, net_amt_precision) != flt(returned_stock_value, net_amt_precision):
cost_of_goods_sold_account = self.get_stock_variance_account(item)
stock_adjustment_amt = stock_amount - warehouse_debit_amount
stock_adjustment_amt = stock_amount - returned_stock_value
gl_entries.append(
self.get_gl_dict(

View File

@@ -844,7 +844,9 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
)
existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0].base_net_amount or 0
pi = make_purchase_invoice(currency="USD", conversion_rate=60, project=project.name)
pi = make_purchase_invoice(currency="USD", conversion_rate=60, project=project.name, do_not_save=True)
pi.credit_to = "_Test Payable USD - _TC"
pi.submit()
self.assertEqual(
frappe.db.get_value("Project", project.name, "total_purchase_cost"),
existing_purchase_cost + 15000,
@@ -856,12 +858,14 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
existing_purchase_cost + 15500,
)
pi1.reload()
pi1.cancel()
self.assertEqual(
frappe.db.get_value("Project", project.name, "total_purchase_cost"),
existing_purchase_cost + 15000,
)
pi.reload()
pi.cancel()
self.assertEqual(
frappe.db.get_value("Project", project.name, "total_purchase_cost"), existing_purchase_cost
@@ -2228,81 +2232,6 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
def test_internal_transfer_invoice_with_rejected_qty(self):
"""An invoice that updates stock moves rejected material out of the in-transit warehouse and
books it, like a receipt does."""
from erpnext.accounts.doctype.sales_invoice.mapper import make_inter_company_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
get_gl_entries,
make_purchase_receipt,
prepare_data_for_internal_transfer,
)
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
prepare_data_for_internal_transfer()
company = "_Test Company with perpetual inventory"
from_warehouse = create_warehouse("_Test Invoice Transfer From", company=company)
transit_warehouse = create_warehouse("_Test Invoice Transfer Transit", company=company)
to_warehouse = create_warehouse("_Test Invoice Transfer To", company=company)
rejected_warehouse = create_warehouse("_Test Invoice Transfer Rejected", company=company)
item_doc = create_item("Test Invoice Internal Transfer Item")
make_purchase_receipt(
item_code=item_doc.name, company=company, warehouse=from_warehouse, qty=10, rate=100
)
si = create_sales_invoice(
company=company,
customer="_Test Internal Customer 2",
item_code=item_doc.name,
qty=10,
rate=100,
warehouse=from_warehouse,
update_stock=1,
cost_center="Main - TCP1",
debit_to="Debtors - TCP1",
income_account="Sales - TCP1",
do_not_save=1,
)
si.items[0].target_warehouse = transit_warehouse
si.insert()
si.submit()
pi = make_inter_company_purchase_invoice(si.name)
pi.update_stock = 1
pi.items[0].warehouse = to_warehouse
pi.items[0].qty = 7
pi.items[0].rejected_qty = 3
pi.items[0].received_qty = 10
pi.items[0].rejected_warehouse = rejected_warehouse
pi.items[0].expense_account = "Cost of Goods Sold - TCP1"
pi.submit()
sl_entries = frappe.get_all(
"Stock Ledger Entry",
filters={"voucher_no": pi.name, "is_cancelled": 0},
fields=["warehouse", "actual_qty", "stock_value_difference"],
)
stock_qty = {d.warehouse: d.actual_qty for d in sl_entries}
self.assertEqual(stock_qty[transit_warehouse], -10)
self.assertEqual(stock_qty[to_warehouse], 7)
self.assertEqual(stock_qty[rejected_warehouse], 3)
booked_value = {}
for entry in get_gl_entries("Purchase Invoice", pi.name, skip_cancelled=True):
booked_value.setdefault(entry.account, 0)
booked_value[entry.account] += flt(entry.debit) - flt(entry.credit)
self.assertEqual(flt(sum(booked_value.values()), 2), 0)
self.assertEqual(booked_value[get_inventory_account(company, transit_warehouse)], -1000)
self.assertEqual(booked_value[get_inventory_account(company, to_warehouse)], 700)
self.assertEqual(booked_value[get_inventory_account(company, rejected_warehouse)], 300)
def test_adjust_incoming_rate_for_rejected_item(self):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
@@ -2699,6 +2628,582 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
return_pi.submit()
self.assertEqual(return_pi.docstatus, 1)
def test_internal_transfer_invoice_with_rejected_qty(self):
"""An invoice that updates stock moves rejected material out of the in-transit warehouse and
books it, like a receipt does."""
from erpnext.accounts.doctype.sales_invoice.mapper import make_inter_company_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
get_gl_entries,
make_purchase_receipt,
prepare_data_for_internal_transfer,
)
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
prepare_data_for_internal_transfer()
company = "_Test Company with perpetual inventory"
from_warehouse = create_warehouse("_Test Invoice Transfer From", company=company)
transit_warehouse = create_warehouse("_Test Invoice Transfer Transit", company=company)
to_warehouse = create_warehouse("_Test Invoice Transfer To", company=company)
rejected_warehouse = create_warehouse("_Test Invoice Transfer Rejected", company=company)
item_doc = create_item("Test Invoice Internal Transfer Item")
make_purchase_receipt(
item_code=item_doc.name, company=company, warehouse=from_warehouse, qty=10, rate=100
)
si = create_sales_invoice(
company=company,
customer="_Test Internal Customer 2",
item_code=item_doc.name,
qty=10,
rate=100,
warehouse=from_warehouse,
update_stock=1,
cost_center="Main - TCP1",
debit_to="Debtors - TCP1",
income_account="Sales - TCP1",
do_not_save=1,
)
si.items[0].target_warehouse = transit_warehouse
si.insert()
si.submit()
pi = make_inter_company_purchase_invoice(si.name)
pi.update_stock = 1
pi.items[0].warehouse = to_warehouse
pi.items[0].qty = 7
pi.items[0].rejected_qty = 3
pi.items[0].received_qty = 10
pi.items[0].rejected_warehouse = rejected_warehouse
pi.items[0].expense_account = "Cost of Goods Sold - TCP1"
pi.submit()
sl_entries = frappe.get_all(
"Stock Ledger Entry",
filters={"voucher_no": pi.name, "is_cancelled": 0},
fields=["warehouse", "actual_qty", "stock_value_difference"],
)
stock_qty = {d.warehouse: d.actual_qty for d in sl_entries}
self.assertEqual(stock_qty[transit_warehouse], -10)
self.assertEqual(stock_qty[to_warehouse], 7)
self.assertEqual(stock_qty[rejected_warehouse], 3)
booked_value = {}
for entry in get_gl_entries("Purchase Invoice", pi.name, skip_cancelled=True):
booked_value.setdefault(entry.account, 0)
booked_value[entry.account] += flt(entry.debit) - flt(entry.credit)
self.assertEqual(flt(sum(booked_value.values()), 2), 0)
self.assertEqual(booked_value[get_inventory_account(company, transit_warehouse)], -1000)
self.assertEqual(booked_value[get_inventory_account(company, to_warehouse)], 700)
self.assertEqual(booked_value[get_inventory_account(company, rejected_warehouse)], 300)
def test_internal_transfer_invoice_with_rejected_batch_qty(self):
"""Batch material rejected on a stock updating internal transfer invoice gets its own package."""
from erpnext.accounts.doctype.sales_invoice.mapper import make_inter_company_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
make_purchase_receipt,
prepare_data_for_internal_transfer,
)
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
prepare_data_for_internal_transfer()
company = "_Test Company with perpetual inventory"
from_warehouse = create_warehouse("_Test Batch Invoice Transfer From", company=company)
transit_warehouse = create_warehouse("_Test Batch Invoice Transfer Transit", company=company)
to_warehouse = create_warehouse("_Test Batch Invoice Transfer To", company=company)
rejected_warehouse = create_warehouse("_Test Batch Invoice Transfer Rejected", company=company)
item = make_item(
"Test Invoice Internal Transfer Batch Item",
{
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "TIITB-.####",
},
)
make_purchase_receipt(
item_code=item.name, company=company, warehouse=from_warehouse, qty=10, rate=100
)
si = create_sales_invoice(
company=company,
customer="_Test Internal Customer 2",
item_code=item.name,
qty=10,
rate=100,
warehouse=from_warehouse,
update_stock=1,
cost_center="Main - TCP1",
debit_to="Debtors - TCP1",
income_account="Sales - TCP1",
do_not_save=1,
)
si.items[0].target_warehouse = transit_warehouse
si.insert()
si.submit()
pi = make_inter_company_purchase_invoice(si.name)
pi.update_stock = 1
pi.items[0].warehouse = to_warehouse
pi.items[0].qty = 7
pi.items[0].rejected_qty = 3
pi.items[0].received_qty = 10
pi.items[0].rejected_warehouse = rejected_warehouse
pi.items[0].expense_account = "Cost of Goods Sold - TCP1"
pi.submit()
row = pi.items[0]
self.assertEqual(
frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "total_qty"), 7
)
self.assertEqual(
frappe.db.get_value("Serial and Batch Bundle", row.rejected_serial_and_batch_bundle, "total_qty"),
3,
)
sl_entries = frappe.get_all(
"Stock Ledger Entry",
filters={"voucher_no": pi.name, "is_cancelled": 0},
fields=["warehouse", "actual_qty", "stock_value_difference"],
)
moved_qty = {d.warehouse: d.actual_qty for d in sl_entries}
moved_value = {d.warehouse: d.stock_value_difference for d in sl_entries}
self.assertEqual(moved_qty[transit_warehouse], -10)
self.assertEqual(moved_qty[to_warehouse], 7)
self.assertEqual(moved_qty[rejected_warehouse], 3)
self.assertEqual(flt(moved_value[transit_warehouse]), -1000)
self.assertEqual(flt(moved_value[to_warehouse]), 700)
self.assertEqual(flt(moved_value[rejected_warehouse]), 300)
def test_internal_transfer_invoice_with_every_unit_rejected(self):
"""An invoice may reject a whole row, and the in-transit warehouse is emptied all the same."""
from erpnext.accounts.doctype.sales_invoice.mapper import make_inter_company_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
make_purchase_receipt,
prepare_data_for_internal_transfer,
)
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
prepare_data_for_internal_transfer()
company = "_Test Company with perpetual inventory"
from_warehouse = create_warehouse("_Test Rejected Invoice From", company=company)
transit_warehouse = create_warehouse("_Test Rejected Invoice Transit", company=company)
to_warehouse = create_warehouse("_Test Rejected Invoice To", company=company)
rejected_warehouse = create_warehouse("_Test Rejected Invoice Rejected", company=company)
item_doc = create_item("Test Fully Rejected Transfer Item")
make_purchase_receipt(
item_code=item_doc.name, company=company, warehouse=from_warehouse, qty=10, rate=100
)
si = create_sales_invoice(
company=company,
customer="_Test Internal Customer 2",
item_code=item_doc.name,
qty=10,
rate=100,
warehouse=from_warehouse,
update_stock=1,
cost_center="Main - TCP1",
debit_to="Debtors - TCP1",
income_account="Sales - TCP1",
do_not_save=1,
)
si.items[0].target_warehouse = transit_warehouse
si.insert()
si.submit()
pi = make_inter_company_purchase_invoice(si.name)
pi.update_stock = 1
pi.items[0].warehouse = to_warehouse
pi.items[0].qty = 0
pi.items[0].rejected_qty = 10
pi.items[0].received_qty = 10
pi.items[0].rejected_warehouse = rejected_warehouse
pi.items[0].expense_account = "Cost of Goods Sold - TCP1"
pi.submit()
sl_entries = frappe.get_all(
"Stock Ledger Entry",
filters={"voucher_no": pi.name, "is_cancelled": 0},
fields=["warehouse", "actual_qty", "stock_value_difference"],
)
moved_qty = {d.warehouse: d.actual_qty for d in sl_entries}
moved_value = {d.warehouse: d.stock_value_difference for d in sl_entries}
self.assertNotIn(to_warehouse, moved_qty)
self.assertEqual(moved_qty[transit_warehouse], -10)
self.assertEqual(moved_qty[rejected_warehouse], 10)
self.assertEqual(flt(moved_value[transit_warehouse]), -1000)
self.assertEqual(flt(moved_value[rejected_warehouse]), 1000)
def test_return_of_an_internal_transfer_invoice_that_rejected_everything(self):
"""Material rejected in full goes back to the in-transit warehouse at the rate it came in
with, and the entries say the same as the stock."""
from erpnext.accounts.doctype.sales_invoice.mapper import make_inter_company_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
make_purchase_receipt,
prepare_data_for_internal_transfer,
)
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
prepare_data_for_internal_transfer()
company = "_Test Company with perpetual inventory"
from_warehouse = create_warehouse("_Test Returned Transfer From", company=company)
transit_warehouse = create_warehouse("_Test Returned Transfer Transit", company=company)
to_warehouse = create_warehouse("_Test Returned Transfer To", company=company)
rejected_warehouse = create_warehouse("_Test Returned Transfer Rejected", company=company)
item_doc = create_item("Test Returned Fully Rejected Transfer Item")
make_purchase_receipt(
item_code=item_doc.name, company=company, warehouse=from_warehouse, qty=10, rate=100
)
si = create_sales_invoice(
company=company,
customer="_Test Internal Customer 2",
item_code=item_doc.name,
qty=10,
rate=100,
warehouse=from_warehouse,
update_stock=1,
cost_center="Main - TCP1",
debit_to="Debtors - TCP1",
income_account="Sales - TCP1",
do_not_save=1,
)
si.items[0].target_warehouse = transit_warehouse
si.insert()
si.submit()
pi = make_inter_company_purchase_invoice(si.name)
pi.update_stock = 1
pi.items[0].warehouse = to_warehouse
pi.items[0].qty = 0
pi.items[0].rejected_qty = 10
pi.items[0].received_qty = 10
pi.items[0].rejected_warehouse = rejected_warehouse
pi.items[0].expense_account = "Cost of Goods Sold - TCP1"
pi.submit()
returned = make_return_doc("Purchase Invoice", pi.name)
returned.update_stock = 1
returned.submit()
moved = {
d.warehouse: flt(d.stock_value_difference)
for d in frappe.get_all(
"Stock Ledger Entry",
filters={"voucher_no": returned.name, "is_cancelled": 0},
fields=["warehouse", "stock_value_difference"],
)
}
self.assertEqual(moved[transit_warehouse], 1000)
self.assertEqual(moved[rejected_warehouse], -1000)
booked = {}
for entry in frappe.get_all(
"GL Entry",
filters={"voucher_no": returned.name, "is_cancelled": 0},
fields=["account", "debit", "credit"],
):
booked.setdefault(entry.account, 0)
booked[entry.account] += flt(entry.debit) - flt(entry.credit)
self.assertEqual(flt(sum(booked.values()), 2), 0)
self.assertEqual(booked[get_inventory_account(company, transit_warehouse)], 1000)
self.assertEqual(booked[get_inventory_account(company, rejected_warehouse)], -1000)
def test_stock_updating_invoice_rejects_every_unit_of_a_row(self):
"""A row of a stock updating invoice may be rejected in full."""
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
company = "_Test Company with perpetual inventory"
item = make_item(properties={"is_stock_item": 1}).name
rejected_warehouse = create_warehouse("_Test Fully Rejected Invoice Warehouse", company=company)
pi = make_purchase_invoice(
company=company,
item_code=item,
warehouse="Stores - TCP1",
qty=0,
rejected_qty=10,
received_qty=10,
rate=100,
rejected_warehouse=rejected_warehouse,
update_stock=1,
expense_account="Cost of Goods Sold - TCP1",
cost_center="Main - TCP1",
do_not_save=True,
)
pi.submit()
moved_qty = {
d.warehouse: d.actual_qty
for d in frappe.get_all(
"Stock Ledger Entry",
filters={"voucher_no": pi.name, "is_cancelled": 0},
fields=["warehouse", "actual_qty"],
)
}
self.assertEqual(moved_qty, {rejected_warehouse: 10})
@ERPNextTestSuite.change_settings(
"Buying Settings",
{
"bill_for_rejected_quantity_in_purchase_invoice": 1,
"set_valuation_rate_for_rejected_materials": 1,
},
)
def test_stock_updating_invoice_bills_the_rejected_quantity(self):
"""With the rejected quantity billed and valued, the invoice pays for every unit received and
the stock it moves matches the entries it books."""
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
company = "_Test Company with perpetual inventory"
item = make_item(properties={"is_stock_item": 1, "valuation_method": "FIFO"}).name
rejected_warehouse = create_warehouse("_Test Invoice Billed Rejected Warehouse", company=company)
pi = make_purchase_invoice(
item_code=item,
company=company,
warehouse="Stores - TCP1",
rejected_warehouse=rejected_warehouse,
cost_center="Main - TCP1",
supplier_warehouse="Work In Progress - TCP1",
expense_account="_Test Account Cost for Goods Sold - TCP1",
update_stock=1,
received_qty=10,
qty=6,
rejected_qty=4,
rate=100,
)
self.assertEqual(pi.items[0].amount, 1000)
self.assertEqual(pi.items[0].valuation_rate, 100)
stock_value = frappe.get_all(
"Stock Ledger Entry",
filters={"voucher_no": pi.name, "is_cancelled": 0},
fields=["warehouse", "stock_value_difference"],
)
by_warehouse = {d.warehouse: d.stock_value_difference for d in stock_value}
self.assertEqual(by_warehouse["Stores - TCP1"], 600)
self.assertEqual(by_warehouse[rejected_warehouse], 400)
booked = frappe.get_all(
"GL Entry", filters={"voucher_no": pi.name, "is_cancelled": 0}, fields=["debit"]
)
self.assertEqual(sum(flt(d.debit) for d in booked), 1000)
@ERPNextTestSuite.change_settings(
"Buying Settings",
{
"bill_for_rejected_quantity_in_purchase_invoice": 1,
"set_valuation_rate_for_rejected_materials": 1,
},
)
def test_rejected_material_is_reposted_after_the_setting_changes(self):
"""The entries an invoice books follow the stock it moved, so they can be built again once
the settings have moved on."""
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
company = "_Test Company with perpetual inventory"
item = make_item(properties={"is_stock_item": 1, "valuation_method": "FIFO"}).name
rejected_warehouse = create_warehouse("_Test Invoice Repost Rejected", company=company)
pi = make_purchase_invoice(
company=company,
item_code=item,
warehouse="Stores - TCP1",
qty=6,
rejected_qty=4,
received_qty=10,
rate=100,
rejected_warehouse=rejected_warehouse,
update_stock=1,
expense_account="Cost of Goods Sold - TCP1",
cost_center="Main - TCP1",
)
frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0)
frappe.db.set_single_value("Buying Settings", "set_valuation_rate_for_rejected_materials", 0)
rebuilt = pi.get_gl_entries()
rejected_account = get_inventory_account(company, rejected_warehouse)
self.assertEqual(
flt(sum(flt(entry.get("debit")) - flt(entry.get("credit")) for entry in rebuilt), 2), 0
)
self.assertEqual(
flt(
sum(flt(entry.get("debit")) for entry in rebuilt if entry.get("account") == rejected_account)
),
400,
)
@ERPNextTestSuite.change_settings(
"Buying Settings",
{
"bill_for_rejected_quantity_in_purchase_invoice": 1,
"set_valuation_rate_for_rejected_materials": 1,
},
)
def test_return_without_a_reference_books_both_warehouses(self):
"""A return that stands on its own gives back the rejected material too, and books it once."""
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
company = "_Test Company with perpetual inventory"
item = make_item(properties={"is_stock_item": 1, "valuation_method": "FIFO"}).name
accepted_warehouse = create_warehouse("_Test Invoice Return Accepted", company=company)
rejected_warehouse = create_warehouse("_Test Invoice Return Rejected", company=company)
def make_invoice(sign):
return make_purchase_invoice(
company=company,
item_code=item,
warehouse=accepted_warehouse,
qty=6 * sign,
rejected_qty=4 * sign,
received_qty=10 * sign,
rate=100,
rejected_warehouse=rejected_warehouse,
update_stock=1,
is_return=1 if sign < 0 else 0,
expense_account="Cost of Goods Sold - TCP1",
cost_center="Main - TCP1",
)
make_invoice(1)
returned = make_invoice(-1)
booked = {}
for entry in frappe.get_all(
"GL Entry",
filters={"voucher_no": returned.name, "is_cancelled": 0},
fields=["account", "debit", "credit"],
):
booked.setdefault(entry.account, 0)
booked[entry.account] += flt(entry.debit) - flt(entry.credit)
self.assertEqual(flt(sum(booked.values()), 2), 0)
self.assertEqual(booked[get_inventory_account(company, accepted_warehouse)], -600)
self.assertEqual(booked[get_inventory_account(company, rejected_warehouse)], -400)
@ERPNextTestSuite.change_settings(
"Buying Settings",
{
"bill_for_rejected_quantity_in_purchase_invoice": 1,
"set_valuation_rate_for_rejected_materials": 1,
},
)
def test_discount_on_an_invoice_that_bills_the_rejected_quantity(self):
"""A discount is spread over every unit the invoice pays for, not the accepted ones alone."""
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
company = "_Test Company with perpetual inventory"
item = make_item(properties={"is_stock_item": 1, "valuation_method": "FIFO"}).name
rejected_warehouse = create_warehouse("_Test Invoice Discount Rejected", company=company)
pi = make_purchase_invoice(
company=company,
item_code=item,
warehouse="Stores - TCP1",
qty=6,
rejected_qty=4,
received_qty=10,
rate=100,
rejected_warehouse=rejected_warehouse,
update_stock=1,
expense_account="Cost of Goods Sold - TCP1",
cost_center="Main - TCP1",
do_not_save=True,
)
pi.apply_discount_on = "Net Total"
pi.additional_discount_percentage = 10
pi.submit()
self.assertEqual(pi.items[0].amount, 1000)
self.assertEqual(pi.items[0].net_rate, 90)
self.assertEqual(pi.grand_total, 900)
self.assertEqual(frappe.db.get_value("Item", item, "last_purchase_rate"), 90)
@ERPNextTestSuite.change_settings(
"Buying Settings",
{
"set_valuation_rate_for_rejected_materials": 1,
"bill_for_rejected_quantity_in_purchase_invoice": 0,
},
)
def test_rejected_material_is_not_valued_on_a_stock_updating_invoice(self):
"""An invoice that does not bill the rejected quantity has nothing to pay for that material,
so it carries no cost and the stock the invoice moves matches the entries it books."""
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
company = "_Test Company with perpetual inventory"
item = make_item(properties={"is_stock_item": 1, "valuation_method": "FIFO"}).name
rejected_warehouse = create_warehouse("_Test Invoice Rejected Warehouse", company=company)
pi = make_purchase_invoice(
item_code=item,
company=company,
warehouse="Stores - TCP1",
rejected_warehouse=rejected_warehouse,
cost_center="Main - TCP1",
supplier_warehouse="Work In Progress - TCP1",
expense_account="_Test Account Cost for Goods Sold - TCP1",
update_stock=1,
received_qty=10,
qty=6,
rejected_qty=4,
rate=100,
)
self.assertEqual(pi.items[0].amount, 600)
stock_value = frappe.get_all(
"Stock Ledger Entry",
filters={"voucher_no": pi.name, "is_cancelled": 0},
fields=["warehouse", "stock_value_difference"],
)
by_warehouse = {d.warehouse: d.stock_value_difference for d in stock_value}
self.assertEqual(by_warehouse["Stores - TCP1"], 600)
self.assertEqual(by_warehouse[rejected_warehouse], 0)
booked = frappe.get_all(
"GL Entry", filters={"voucher_no": pi.name, "is_cancelled": 0}, fields=["debit"]
)
self.assertEqual(sum(flt(d.debit) for d in booked), sum(by_warehouse.values()))
def test_purchase_invoice_with_use_serial_batch_field_for_rejected_qty(self):
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse

View File

@@ -6,6 +6,7 @@
"description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain a list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\", etc.",
"doctype": "DocType",
"document_type": "Setup",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/purchase-taxes-and-charges-template",
"engine": "InnoDB",
"field_order": [
"title",
@@ -74,7 +75,7 @@
"options": "Tax Category"
}
],
"icon": "fa fa-money",
"icon": "badge-percent",
"idx": 1,
"links": [],
"modified": "2026-08-21 23:11:47.506282",

View File

@@ -4,6 +4,7 @@
"creation": "2023-07-04 13:07:32.923675",
"default_view": "List",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/repost-accounting-ledger",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -101,6 +102,7 @@
"read_only": 1
}
],
"icon": "list-restart",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],

View File

@@ -95,6 +95,7 @@
"label": "Repost Error Log"
}
],
"icon": "list-restart",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],

View File

@@ -4,6 +4,7 @@
"autoname": "naming_series:",
"creation": "2022-01-25 10:29:57.771398",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/sales-invoice",
"engine": "InnoDB",
"field_order": [
"customer_section",
@@ -2395,7 +2396,7 @@
}
],
"grid_page_length": 50,
"icon": "fa fa-file-text",
"icon": "receipt-text",
"idx": 181,
"is_submittable": 1,
"links": [

View File

@@ -6,6 +6,7 @@
"description": "Standard tax template that can be applied to all Sales Transactions. This template can contain a list of tax heads and also other expense/income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.",
"doctype": "DocType",
"document_type": "Setup",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/sales-taxes-and-charges-template",
"engine": "InnoDB",
"field_order": [
"title",
@@ -76,7 +77,7 @@
"options": "Tax Category"
}
],
"icon": "fa fa-money",
"icon": "badge-percent",
"idx": 1,
"links": [],
"modified": "2026-08-21 23:11:48.797423",

View File

@@ -3,6 +3,7 @@
"autoname": "ACC-SHT-.YYYY.-.#####",
"creation": "2017-12-25 17:18:03.143726",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/share-transfer",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -190,6 +191,7 @@
"fieldtype": "Column Break"
}
],
"icon": "arrow-right-left",
"is_submittable": 1,
"links": [],
"modified": "2024-03-27 13:10:40.003178",

View File

@@ -24,6 +24,7 @@
"label": "Description"
}
],
"icon": "tag",
"links": [],
"modified": "2024-03-27 13:10:40.190914",
"modified_by": "Administrator",

View File

@@ -3,6 +3,7 @@
"autoname": "naming_series:",
"creation": "2017-12-25 16:50:53.878430",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/shareholder",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -112,6 +113,7 @@
"read_only": 1
}
],
"icon": "user-star",
"links": [],
"modified": "2024-03-27 13:10:40.356171",
"modified_by": "Administrator",

View File

@@ -5,6 +5,7 @@
"creation": "2013-06-25 11:48:03",
"description": "Specify conditions to calculate shipping amount",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/shipping-rule",
"engine": "InnoDB",
"field_order": [
"label",
@@ -146,7 +147,7 @@
"options": "Project"
}
],
"icon": "fa fa-truck",
"icon": "truck",
"idx": 1,
"links": [],
"modified": "2026-08-21 23:11:49.532098",

View File

@@ -3,6 +3,7 @@
"autoname": "ACC-SUB-.YYYY.-.#####",
"creation": "2017-07-18 17:50:43.967266",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/subscription",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -299,6 +300,7 @@
"fieldtype": "Section Break"
}
],
"icon": "calendar-sync",
"index_web_pages_for_search": 1,
"links": [
{

View File

@@ -4,6 +4,7 @@
"autoname": "field:plan_name",
"creation": "2018-02-24 11:31:23.066506",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/subscription-plan",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -148,6 +149,7 @@
"label": "Product Price ID"
}
],
"icon": "calendar-sync",
"links": [],
"modified": "2024-03-27 13:10:47.998597",
"modified_by": "Administrator",

View File

@@ -2,6 +2,7 @@
"actions": [],
"creation": "2018-02-26 06:13:37.910139",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/subscription-settings",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -32,6 +33,7 @@
],
"grid_page_length": 50,
"hide_toolbar": 0,
"icon": "settings",
"issingle": 1,
"links": [],
"modified": "2026-03-16 13:28:20.485964",

View File

@@ -4,6 +4,7 @@
"autoname": "field:title",
"creation": "2022-01-19 01:09:28.920486",
"doctype": "DocType",
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/tax-category",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -27,6 +28,7 @@
"label": "Disabled"
}
],
"icon": "badge-percent",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2026-08-21 23:11:50.740962",

Some files were not shown because too many files have changed in this diff Show More