Skip to content

feat: add opening stock dialog for stock items#54570

Open
khushi8112 wants to merge 3 commits intofrappe:developfrom
khushi8112:item-opening-stock-dialog
Open

feat: add opening stock dialog for stock items#54570
khushi8112 wants to merge 3 commits intofrappe:developfrom
khushi8112:item-opening-stock-dialog

Conversation

@khushi8112
Copy link
Copy Markdown
Member

@khushi8112 khushi8112 commented Apr 28, 2026

Why this change:

The inline field was limited, it only worked at item creation, had no company/warehouse visibility, and completely broke for serial/batch items. Users who forgot to set opening stock had no way to do it from the item form without manually creating a Stock Entry.

What changed

  • opening_stock inline field is now hidden on saved items
  • New Set Opening Stock button added under Actions menu (only visible on saved stock items with no existing stock ledger)
  • Clicking the button opens a dialog with company selector, auto-filled warehouse from Item Defaults, qty, valuation rate
  • Serial/batch items are handled, dialog surfaces series fields as required inputs when has_serial_no or has_batch_no is enabled
  • create_new_batch is locked as checked in the dialog for batch items to prevent silent failures
  • Success toast shown via Python-side frappe.msgprint with a direct link to the created Stock Entry
Screen.Recording.2026-04-29.at.1.10.39.AM.mov

no-docs

@github-actions github-actions Bot added the needs-tests This PR needs automated unit-tests. label Apr 28, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 28, 2026

Codecov Report

❌ Patch coverage is 8.51064% with 43 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.71%. Comparing base (b001884) to head (1890ccd).
⚠️ Report is 13 commits behind head on develop.

Files with missing lines Patch % Lines
erpnext/stock/doctype/item/item.py 8.51% 43 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #54570      +/-   ##
===========================================
- Coverage    79.74%   79.71%   -0.03%     
===========================================
  Files         1160     1160              
  Lines       126362   126427      +65     
===========================================
+ Hits        100768   100787      +19     
- Misses       25594    25640      +46     
Files with missing lines Coverage Δ
erpnext/stock/doctype/item/item.py 78.73% <8.51%> (-5.40%) ⬇️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@khushi8112 khushi8112 force-pushed the item-opening-stock-dialog branch from 76628d0 to b7d6ed5 Compare April 28, 2026 19:43
@khushi8112 khushi8112 marked this pull request as ready for review April 28, 2026 19:46
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

📝 Walkthrough

Walkthrough

The PR replaces the conditional opening_stock form field with a dialog-driven "Set Opening Stock" workflow. The Item DocType now hides opening_stock and reorders image. On Item refresh for stock items, a "Set Opening Stock" button is shown when the user can create/write Stock Entries and no stock exists. Clicking it opens a dialog to capture quantity, valuation rate, and optional serial/batch series; submitting calls a new server method make_opening_stock_entry which resolves a warehouse, persists serial/batch settings to the Item, creates and submits a Material Receipt Stock Entry, and returns the entry name.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add opening stock dialog for stock items' accurately and concisely summarizes the main change—introducing a new dialog interface for setting opening stock on stock items.
Description check ✅ Passed The description clearly explains the motivation (limitations of the previous inline field), the key changes (hiding the field, adding a dialog with company/warehouse selection, handling serial/batch items), and the success behavior.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@erpnext/stock/doctype/item/item.js`:
- Around line 162-173: The "Set Opening Stock" button is being added for
unsaved/new items; update the condition that wraps frm.add_custom_button so it
only runs for persisted items by checking the doc is not local (e.g., require
!frm.doc.__islocal or an equivalent saved-doc check). In practice, modify the
existing if that uses can_create_stock_entry and has_existing_stock to also
require the item be saved (reference the can_create_stock_entry and
has_existing_stock variables and the call to
erpnext.item.show_opening_stock_dialog) so the button is only added for saved
items.
- Around line 708-720: The current show_opening_stock_dialog function aborts
when frm.doc.item_defaults is empty; remove the early msgprint/return and
instead allow the dialog flow to continue even if companies array is empty so
the server-side fallback (Stock Settings/"Stores") can resolve a warehouse.
Specifically, update show_opening_stock_dialog to stop treating
(frm.doc.item_defaults || []).map(...) producing no companies as a hard
error—either omit the msgprint/return or replace it by continuing with companies
= [] and ensuring the subsequent dialog/request uses that empty companies value
so backend resolution can occur.

In `@erpnext/stock/doctype/item/item.json`:
- Around line 243-245: The tour step in erpnext/stock/doctype/item/item.js
currently targets the now-hidden field "opening_stock" and must be updated:
locate the Item tour definition (the TOUR_STEPS or initTour block around the
step targeting "opening_stock") and either change its selector/target to the new
"Set Opening Stock" action element (use the action's CSS selector or data-action
attribute) or remove that step entirely; ensure the step text/context still
makes sense after changing the target and that the tour initialization (function
names like init_item_tour or the TOUR_STEPS array) is updated accordingly.

In `@erpnext/stock/doctype/item/item.py`:
- Around line 1565-1574: The Stock Settings fallback used by
get_default_warehouse_for_opening_stock() must be company-safe: when reading
frappe.get_single_value("Stock Settings", "default_warehouse") check that the
returned warehouse's company matches the target company (use
frappe.db.get_value("Warehouse", default_wh, "company") or similar); if it
belongs to a different company, ignore it and continue to the existing fallback
(frappe.db.get_value("Warehouse", {"warehouse_name": _("Stores"), "company":
company})). Ensure this logic is applied where
get_default_warehouse_for_opening_stock() is used (and mirrors the previous
Item.set_opening_stock() behaviour) so opening stock never resolves to a
warehouse in another company.
- Around line 1489-1493: Before allowing an Opening Stock entry to be created,
re-check the item's ledger state by calling item.stock_ledger_created() after
loading the Item doc (the existing code uses item = frappe.get_doc("Item",
item_code) and currently only checks item.is_stock_item); if
item.stock_ledger_created() returns True, raise an exception (frappe.throw)
rejecting the Opening Stock creation with an appropriate message so backend
cannot be bypassed by stale client state or direct RPC.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: b608296b-c899-45e4-b183-a67148f7e849

📥 Commits

Reviewing files that changed from the base of the PR and between a8030c9 and b7d6ed5.

📒 Files selected for processing (3)
  • erpnext/stock/doctype/item/item.js
  • erpnext/stock/doctype/item/item.json
  • erpnext/stock/doctype/item/item.py

Comment thread erpnext/stock/doctype/item/item.js
Comment on lines +708 to +720
show_opening_stock_dialog: function (frm) {
const companies = (frm.doc.item_defaults || []).map((d) => d.company).filter(Boolean);

if (!companies.length) {
frappe.msgprint({
title: __("No Company Found"),
message: __(
"Please add at least one row in Item Defaults with a Company before setting opening stock."
),
indicator: "orange",
});
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't make Item Defaults mandatory for this workflow.

The backend already falls back from Item Defaults to Stock Settings / "Stores", but the dialog exits immediately unless at least one item_defaults row exists. That means saved stock items without Item Defaults never get the new opening-stock flow even though the server path can resolve a warehouse for them.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@erpnext/stock/doctype/item/item.js` around lines 708 - 720, The current
show_opening_stock_dialog function aborts when frm.doc.item_defaults is empty;
remove the early msgprint/return and instead allow the dialog flow to continue
even if companies array is empty so the server-side fallback (Stock
Settings/"Stores") can resolve a warehouse. Specifically, update
show_opening_stock_dialog to stop treating (frm.doc.item_defaults ||
[]).map(...) producing no companies as a hard error—either omit the
msgprint/return or replace it by continuing with companies = [] and ensuring the
subsequent dialog/request uses that empty companies value so backend resolution
can occur.

Comment on lines +1489 to +1493
item = frappe.get_doc("Item", item_code)

if not item.is_stock_item:
frappe.throw(_("Opening Stock can only be set for stock items."))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reject "opening stock" once ledger activity already exists.

This endpoint trusts the client-side stock_exists gate, so a stale form or direct RPC can still create another "Opening Stock" material receipt after transactions already exist for the item. Please recheck item.stock_ledger_created() here before persisting fields or inserting the stock entry.

Suggested fix
 	item = frappe.get_doc("Item", item_code)

 	if not item.is_stock_item:
 		frappe.throw(_("Opening Stock can only be set for stock items."))
+
+	if item.stock_ledger_created():
+		frappe.throw(_("Opening stock can only be set before the first stock transaction."))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
item = frappe.get_doc("Item", item_code)
if not item.is_stock_item:
frappe.throw(_("Opening Stock can only be set for stock items."))
item = frappe.get_doc("Item", item_code)
if not item.is_stock_item:
frappe.throw(_("Opening Stock can only be set for stock items."))
if item.stock_ledger_created():
frappe.throw(_("Opening stock can only be set before the first stock transaction."))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@erpnext/stock/doctype/item/item.py` around lines 1489 - 1493, Before allowing
an Opening Stock entry to be created, re-check the item's ledger state by
calling item.stock_ledger_created() after loading the Item doc (the existing
code uses item = frappe.get_doc("Item", item_code) and currently only checks
item.is_stock_item); if item.stock_ledger_created() returns True, raise an
exception (frappe.throw) rejecting the Opening Stock creation with an
appropriate message so backend cannot be bypassed by stale client state or
direct RPC.

Comment on lines +1565 to +1574
target = frappe.get_single_value("Stock Settings", "default_warehouse") or frappe.db.get_value(
"Warehouse", {"warehouse_name": _("Stores"), "company": company}
)

if not target:
frappe.throw(
_(
"No warehouse found for company {0}. Please set a Default Warehouse in Item Defaults or Stock Settings."
).format(frappe.bold(company))
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep the Stock Settings fallback company-safe.

get_default_warehouse_for_opening_stock() now accepts Stock Settings.default_warehouse without checking its company. In a multi-company setup, that can point opening stock for Company B at Company A's warehouse and fail the transaction, whereas Item.set_opening_stock() previously fell back to that company's "Stores" warehouse in this case.

Suggested fix
-	target = frappe.get_single_value("Stock Settings", "default_warehouse") or frappe.db.get_value(
-		"Warehouse", {"warehouse_name": _("Stores"), "company": company}
-	)
+	target = frappe.get_single_value("Stock Settings", "default_warehouse")
+	if target and frappe.db.get_value("Warehouse", target, "company") != company:
+		target = None
+
+	if not target:
+		target = frappe.db.get_value("Warehouse", {"warehouse_name": _("Stores"), "company": company})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
target = frappe.get_single_value("Stock Settings", "default_warehouse") or frappe.db.get_value(
"Warehouse", {"warehouse_name": _("Stores"), "company": company}
)
if not target:
frappe.throw(
_(
"No warehouse found for company {0}. Please set a Default Warehouse in Item Defaults or Stock Settings."
).format(frappe.bold(company))
)
target = frappe.get_single_value("Stock Settings", "default_warehouse")
if target and frappe.db.get_value("Warehouse", target, "company") != company:
target = None
if not target:
target = frappe.db.get_value("Warehouse", {"warehouse_name": _("Stores"), "company": company})
if not target:
frappe.throw(
_(
"No warehouse found for company {0}. Please set a Default Warehouse in Item Defaults or Stock Settings."
).format(frappe.bold(company))
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@erpnext/stock/doctype/item/item.py` around lines 1565 - 1574, The Stock
Settings fallback used by get_default_warehouse_for_opening_stock() must be
company-safe: when reading frappe.get_single_value("Stock Settings",
"default_warehouse") check that the returned warehouse's company matches the
target company (use frappe.db.get_value("Warehouse", default_wh, "company") or
similar); if it belongs to a different company, ignore it and continue to the
existing fallback (frappe.db.get_value("Warehouse", {"warehouse_name":
_("Stores"), "company": company})). Ensure this logic is applied where
get_default_warehouse_for_opening_stock() is used (and mirrors the previous
Item.set_opening_stock() behaviour) so opening stock never resolves to a
warehouse in another company.

@rohitwaghchaure
Copy link
Copy Markdown
Collaborator

@khushi8112 Opening stock is a one-time activity and after that the normal flow will be used. We have added the opening_stock field so that the opening stock entry can be created automatically during the item creation (either manually or through data import)

Even for the people who forgot to add opening stock while creating the item can use the Stock Reconciliation feature to add opening stock.

image

@khushi8112
Copy link
Copy Markdown
Member Author

Opening stock is a one-time activity and after that the normal flow will be used. We have added the opening_stock field so that the opening stock entry can be created automatically during the item creation (either manually or through data import)

Even for the people who forgot to add opening stock while creating the item can use the Stock Reconciliation feature to add opening stock.

I get the idea, but this still has some issues. There’s no company or warehouse context here, which matters even for opening stock.
Also, for serial/batch items, opening stock isn’t just a quantity, it needs proper serial/batch details, so this approach ends up breaking in those cases.

@rohitwaghchaure
Copy link
Copy Markdown
Collaborator

@khushi8112 The pop-up still has issues because it does not allow users to add serial numbers or batches manually. It would be better to redirect users to the Stock Reconciliation form when they click on "Set / Add Opening Stock", with the item details pre-filled and the purpose set to "Opening Stock".

@khushi8112 khushi8112 force-pushed the item-opening-stock-dialog branch from b7d6ed5 to 1890ccd Compare April 29, 2026 13:16
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
erpnext/stock/doctype/item/item.js (1)

714-726: ⚠️ Potential issue | 🟠 Major

Don’t block the dialog when item_defaults is empty.

This hard return still makes the feature unusable for stock items that have no Item Defaults row. The server already has warehouse fallback logic in erpnext/stock/doctype/item/item.py:get_default_warehouse_for_opening_stock, so the dialog should source company independently instead of treating Item Defaults as a prerequisite.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@erpnext/stock/doctype/item/item.js` around lines 714 - 726, The current
show_opening_stock_dialog function aborts when frm.doc.item_defaults yields no
companies; instead remove the early return and determine company independently:
if companies is empty, set companies to a single-entry array from
frm.doc.company (if present) or invoke the existing server fallback (call the
server method get_default_warehouse_for_opening_stock in
erpnext/stock/doctype/item/item.py) to obtain the appropriate warehouse/company
to use; then continue with the dialog flow. Ensure you update the logic around
the companies variable in show_opening_stock_dialog so the dialog is not blocked
when item_defaults is empty.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@erpnext/stock/doctype/item/item.js`:
- Around line 803-838: The dialog always sets create_new_batch to 1 and passes
it into make_opening_stock_entry which causes
persist_serial_batch_fields_for_opening_stock to update the Item record
permanently; change the dialog handling so it does not persist one-off choices:
pass create_new_batch and batch_number_series only as transient args for the
stock entry (avoid calling persist_*), or send a flag like
persist_serial_batch_fields=false to make_opening_stock_entry and ensure
make_opening_stock_entry (and persist_serial_batch_fields_for_opening_stock)
only persists when an explicit “save default” action is taken; update references
to create_new_batch, batch_number_series, make_opening_stock_entry and
persist_serial_batch_fields_for_opening_stock accordingly to prevent silent
Item-level changes from this dialog.

In `@erpnext/stock/doctype/item/item.json`:
- Around line 256-258: The opening_stock field in item.json is hidden
unconditionally, which removes it from the create form even though after_insert
-> set_opening_stock() in erpnext/stock/doctype/item/item.py still expects it;
change the field to be visible on new items and only hidden after the Item is
saved—remove or replace "hidden": 1 with logic that toggles visibility based on
document state (e.g., use "read_only": 1 or a form script/depends_on that hides
the field when docname exists), and ensure references to opening_stock in
set_opening_stock() and after_insert continue to work.

---

Duplicate comments:
In `@erpnext/stock/doctype/item/item.js`:
- Around line 714-726: The current show_opening_stock_dialog function aborts
when frm.doc.item_defaults yields no companies; instead remove the early return
and determine company independently: if companies is empty, set companies to a
single-entry array from frm.doc.company (if present) or invoke the existing
server fallback (call the server method get_default_warehouse_for_opening_stock
in erpnext/stock/doctype/item/item.py) to obtain the appropriate
warehouse/company to use; then continue with the dialog flow. Ensure you update
the logic around the companies variable in show_opening_stock_dialog so the
dialog is not blocked when item_defaults is empty.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 277920e8-c559-4809-a42f-ea2aebacd7c3

📥 Commits

Reviewing files that changed from the base of the PR and between b7d6ed5 and 1890ccd.

📒 Files selected for processing (3)
  • erpnext/stock/doctype/item/item.js
  • erpnext/stock/doctype/item/item.json
  • erpnext/stock/doctype/item/item.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • erpnext/stock/doctype/item/item.py

Comment on lines +803 to +838
{
label: __("Automatically Create New Batch"),
fieldname: "create_new_batch",
fieldtype: "Check",
default: 1,
read_only: 1,
},
{
label: __("Batch Number Series"),
fieldname: "batch_number_series",
fieldtype: "Data",
default: frm.doc.batch_number_series || "",
reqd: 1,
description: __(
"Example: BATCH-.YYYY.-.#####. - A new batch will be auto-created from this series."
),
}
);
}

const dialog = new frappe.ui.Dialog({
title: __("Set Opening Stock"),
fields: fields,
primary_action_label: __("Create Stock Entry"),
primary_action: function (values) {
frappe.call({
method: "erpnext.stock.doctype.item.item.make_opening_stock_entry",
args: {
item_code: frm.doc.name,
company: values.company,
qty: values.qty,
valuation_rate: values.valuation_rate || 0,
warehouse: values.warehouse || null,
serial_no_series: values.serial_no_series || null,
create_new_batch: values.create_new_batch ? 1 : 0,
batch_number_series: values.batch_number_series || null,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid permanently enabling auto batch creation from this one-off dialog.

For batch items this always submits create_new_batch: 1, and make_opening_stock_entry() persists that back to the Item via persist_serial_batch_fields_for_opening_stock(...). Using a one-time opening-stock action will therefore silently change future batch behavior for the Item, which is a much bigger side effect than creating the initial stock entry.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@erpnext/stock/doctype/item/item.js` around lines 803 - 838, The dialog always
sets create_new_batch to 1 and passes it into make_opening_stock_entry which
causes persist_serial_batch_fields_for_opening_stock to update the Item record
permanently; change the dialog handling so it does not persist one-off choices:
pass create_new_batch and batch_number_series only as transient args for the
stock entry (avoid calling persist_*), or send a flag like
persist_serial_batch_fields=false to make_opening_stock_entry and ensure
make_opening_stock_entry (and persist_serial_batch_fields_for_opening_stock)
only persists when an explicit “save default” action is taken; update references
to create_new_batch, batch_number_series, make_opening_stock_entry and
persist_serial_batch_fields_for_opening_stock accordingly to prevent silent
Item-level changes from this dialog.

Comment on lines 256 to +258
"fieldname": "opening_stock",
"fieldtype": "Float",
"hidden": 1,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep opening_stock available on new Items.

"hidden": 1 removes the inline field during initial Item creation too, so users lose the existing create-time opening-stock flow even though after_insert / set_opening_stock() still consumes opening_stock in erpnext/stock/doctype/item/item.py. This should be hidden only after the Item has been saved.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@erpnext/stock/doctype/item/item.json` around lines 256 - 258, The
opening_stock field in item.json is hidden unconditionally, which removes it
from the create form even though after_insert -> set_opening_stock() in
erpnext/stock/doctype/item/item.py still expects it; change the field to be
visible on new items and only hidden after the Item is saved—remove or replace
"hidden": 1 with logic that toggles visibility based on document state (e.g.,
use "read_only": 1 or a form script/depends_on that hides the field when docname
exists), and ensure references to opening_stock in set_opening_stock() and
after_insert continue to work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-tests This PR needs automated unit-tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants