Merge pull request #36083 from frappe/develop

chore: release v15 beta
This commit is contained in:
Ankush Menat
2023-07-11 15:26:00 +05:30
committed by GitHub
33 changed files with 356 additions and 124 deletions

View File

@@ -9,7 +9,7 @@ on:
workflow_dispatch:
jobs:
release:
stable-release:
name: Release
runs-on: ubuntu-latest
strategy:
@@ -30,3 +30,23 @@ jobs:
head: version-${{ matrix.version }}-hotfix
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
beta-release:
name: Release
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: octokit/request-action@v2.x
with:
route: POST /repos/{owner}/{repo}/pulls
owner: frappe
repo: erpnext
title: |-
"chore: release v15 beta"
body: "Automated beta release."
base: version-15-beta
head: develop
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}

View File

@@ -8,9 +8,6 @@ frappe.ui.form.on('Bank', {
},
refresh: function(frm) {
add_fields_to_mapping_table(frm);
frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Bank' };
frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
if (frm.doc.__islocal) {

View File

@@ -93,6 +93,12 @@ class ExchangeRateRevaluation(Document):
return True
def fetch_and_calculate_accounts_data(self):
accounts = self.get_accounts_data()
if accounts:
for acc in accounts:
self.append("accounts", acc)
@frappe.whitelist()
def get_accounts_data(self):
self.validate_mandatory()
@@ -252,8 +258,8 @@ class ExchangeRateRevaluation(Document):
new_balance_in_base_currency = 0
new_balance_in_account_currency = 0
current_exchange_rate = calculate_exchange_rate_using_last_gle(
company, d.account, d.party_type, d.party
current_exchange_rate = (
calculate_exchange_rate_using_last_gle(company, d.account, d.party_type, d.party) or 0.0
)
gain_loss = new_balance_in_account_currency - (

View File

@@ -35,6 +35,7 @@
{
"fieldname": "company",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"label": "Company",
"options": "Company",
@@ -56,7 +57,7 @@
}
],
"links": [],
"modified": "2022-01-18 21:11:23.105589",
"modified": "2023-07-09 18:11:23.105589",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Item Tax Template",
@@ -102,4 +103,4 @@
"states": [],
"title_field": "title",
"track_changes": 1
}
}

View File

@@ -237,10 +237,9 @@ class PaymentEntry(AccountsController):
_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
)
# The reference has already been partly paid
elif (
latest.outstanding_amount < latest.invoice_amount
and flt(d.outstanding_amount, d.precision("outstanding_amount")) != latest.outstanding_amount
):
elif latest.outstanding_amount < latest.invoice_amount and flt(
d.outstanding_amount, d.precision("outstanding_amount")
) != flt(latest.outstanding_amount, d.precision("outstanding_amount")):
frappe.throw(
_(
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
@@ -1433,6 +1432,9 @@ def get_outstanding_reference_documents(args, validate=False):
if args.get("party_type") == "Member":
return
if not args.get("get_outstanding_invoices") and not args.get("get_orders_to_be_billed"):
args["get_outstanding_invoices"] = True
ple = qb.DocType("Payment Ledger Entry")
common_filter = []
accounting_dimensions_filter = []
@@ -1627,60 +1629,59 @@ def get_orders_to_be_billed(
cost_center=None,
filters=None,
):
voucher_type = None
if party_type == "Customer":
voucher_type = "Sales Order"
elif party_type == "Supplier":
voucher_type = "Purchase Order"
elif party_type == "Employee":
voucher_type = None
if not voucher_type:
return []
# Add cost center condition
if voucher_type:
doc = frappe.get_doc({"doctype": voucher_type})
condition = ""
if doc and hasattr(doc, "cost_center") and doc.cost_center:
condition = " and cost_center='%s'" % cost_center
doc = frappe.get_doc({"doctype": voucher_type})
condition = ""
if doc and hasattr(doc, "cost_center") and doc.cost_center:
condition = " and cost_center='%s'" % cost_center
orders = []
if voucher_type:
if party_account_currency == company_currency:
grand_total_field = "base_grand_total"
rounded_total_field = "base_rounded_total"
else:
grand_total_field = "grand_total"
rounded_total_field = "rounded_total"
if party_account_currency == company_currency:
grand_total_field = "base_grand_total"
rounded_total_field = "base_rounded_total"
else:
grand_total_field = "grand_total"
rounded_total_field = "rounded_total"
orders = frappe.db.sql(
"""
select
name as voucher_no,
if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
(if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount,
transaction_date as posting_date
from
`tab{voucher_type}`
where
{party_type} = %s
and docstatus = 1
and company = %s
and ifnull(status, "") != "Closed"
and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
and abs(100 - per_billed) > 0.01
{condition}
order by
transaction_date, name
""".format(
**{
"rounded_total_field": rounded_total_field,
"grand_total_field": grand_total_field,
"voucher_type": voucher_type,
"party_type": scrub(party_type),
"condition": condition,
}
),
(party, company),
as_dict=True,
)
orders = frappe.db.sql(
"""
select
name as voucher_no,
if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
(if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount,
transaction_date as posting_date
from
`tab{voucher_type}`
where
{party_type} = %s
and docstatus = 1
and company = %s
and ifnull(status, "") != "Closed"
and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
and abs(100 - per_billed) > 0.01
{condition}
order by
transaction_date, name
""".format(
**{
"rounded_total_field": rounded_total_field,
"grand_total_field": grand_total_field,
"voucher_type": voucher_type,
"party_type": scrub(party_type),
"condition": condition,
}
),
(party, company),
as_dict=True,
)
order_list = []
for d in orders:
@@ -1713,6 +1714,8 @@ def get_negative_outstanding_invoices(
cost_center=None,
condition=None,
):
if party_type not in ["Customer", "Supplier"]:
return []
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
account = "debit_to" if voucher_type == "Sales Invoice" else "credit_to"
supplier_condition = ""

View File

@@ -549,6 +549,7 @@
"depends_on": "update_stock",
"fieldname": "rejected_warehouse",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Rejected Warehouse",
"no_copy": 1,
"options": "Warehouse",
@@ -1576,7 +1577,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2023-06-03 16:21:54.637245",
"modified": "2023-07-04 17:22:59.145031",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -423,6 +423,7 @@
{
"fieldname": "rejected_warehouse",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Rejected Warehouse",
"options": "Warehouse"
},
@@ -904,7 +905,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2023-07-02 18:39:41.495723",
"modified": "2023-07-04 17:22:21.501152",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@@ -3,8 +3,6 @@
frappe.ui.form.on('Shareholder', {
refresh: function(frm) {
frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Shareholder' };
frm.toggle_display(['contact_html'], !frm.doc.__islocal);
if (frm.doc.__islocal) {

View File

@@ -850,7 +850,7 @@ def get_held_invoices(party_type, party):
if party_type == "Supplier":
held_invoices = frappe.db.sql(
"select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()",
"select name from `tabPurchase Invoice` where on_hold = 1 and release_date IS NOT NULL and release_date > CURDATE()",
as_dict=1,
)
held_invoices = set(d["name"] for d in held_invoices)
@@ -1408,6 +1408,50 @@ def check_and_delete_linked_reports(report):
frappe.delete_doc("Desktop Icon", icon)
def create_err_and_its_journals(companies: list = None) -> None:
if companies:
for company in companies:
err = frappe.new_doc("Exchange Rate Revaluation")
err.company = company.name
err.posting_date = nowdate()
err.rounding_loss_allowance = 0.0
err.fetch_and_calculate_accounts_data()
if err.accounts:
err.save().submit()
response = err.make_jv_entries()
if company.submit_err_jv:
jv = response.get("revaluation_jv", None)
jv and frappe.get_doc("Journal Entry", jv).submit()
jv = response.get("zero_balance_jv", None)
jv and frappe.get_doc("Journal Entry", jv).submit()
def auto_create_exchange_rate_revaluation_daily() -> None:
"""
Executed by background job
"""
companies = frappe.db.get_all(
"Company",
filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Daily"},
fields=["name", "submit_err_jv"],
)
create_err_and_its_journals(companies)
def auto_create_exchange_rate_revaluation_weekly() -> None:
"""
Executed by background job
"""
companies = frappe.db.get_all(
"Company",
filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Weekly"},
fields=["name", "submit_err_jv"],
)
create_err_and_its_journals(companies)
def get_payment_ledger_entries(gl_entries, cancel=0):
ple_map = []
if gl_entries:

View File

@@ -40,6 +40,7 @@ def post_depreciation_entries(date=None):
date = today()
failed_asset_names = []
error_log_names = []
for asset_name in get_depreciable_assets(date):
asset_doc = frappe.get_doc("Asset", asset_name)
@@ -50,10 +51,12 @@ def post_depreciation_entries(date=None):
except Exception as e:
frappe.db.rollback()
failed_asset_names.append(asset_name)
error_log = frappe.log_error(e)
error_log_names.append(error_log.name)
if failed_asset_names:
set_depr_entry_posting_status_for_failed_assets(failed_asset_names)
notify_depr_entry_posting_error(failed_asset_names)
notify_depr_entry_posting_error(failed_asset_names, error_log_names)
frappe.db.commit()
@@ -239,7 +242,7 @@ def set_depr_entry_posting_status_for_failed_assets(failed_asset_names):
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Failed")
def notify_depr_entry_posting_error(failed_asset_names):
def notify_depr_entry_posting_error(failed_asset_names, error_log_names):
recipients = get_users_with_role("Accounts Manager")
if not recipients:
@@ -247,7 +250,8 @@ def notify_depr_entry_posting_error(failed_asset_names):
subject = _("Error while posting depreciation entries")
asset_links = get_comma_separated_asset_links(failed_asset_names)
asset_links = get_comma_separated_links(failed_asset_names, "Asset")
error_log_links = get_comma_separated_links(error_log_names, "Error Log")
message = (
_("Hello,")
@@ -257,23 +261,26 @@ def notify_depr_entry_posting_error(failed_asset_names):
)
+ "."
+ "<br><br>"
+ _(
"Please raise a support ticket and share this email, or forward this email to your development team so that they can find the issue in the developer console by manually creating the depreciation entry via the asset's depreciation schedule table."
+ _("Here are the error logs for the aforementioned failed depreciation entries: {0}").format(
error_log_links
)
+ "."
+ "<br><br>"
+ _("Please share this email with your support team so that they can find and fix the issue.")
)
frappe.sendmail(recipients=recipients, subject=subject, message=message)
def get_comma_separated_asset_links(asset_names):
asset_links = []
def get_comma_separated_links(names, doctype):
links = []
for asset_name in asset_names:
asset_links.append(get_link_to_form("Asset", asset_name))
for name in names:
links.append(get_link_to_form(doctype, name))
asset_links = ", ".join(asset_links)
links = ", ".join(links)
return asset_links
return links
@frappe.whitelist()

View File

@@ -66,8 +66,6 @@ frappe.ui.form.on("Supplier", {
},
refresh: function (frm) {
frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Supplier' }
if (frappe.defaults.get_default("supp_master_name") != "Naming Series") {
frm.toggle_display("naming_series", false);
} else {

View File

@@ -437,18 +437,23 @@ class BuyingController(SubcontractingController):
# validate rate with ref PR
def validate_rejected_warehouse(self):
for d in self.get("items"):
if flt(d.rejected_qty) and not d.rejected_warehouse:
for item in self.get("items"):
if flt(item.rejected_qty) and not item.rejected_warehouse:
if self.rejected_warehouse:
d.rejected_warehouse = self.rejected_warehouse
item.rejected_warehouse = self.rejected_warehouse
if not d.rejected_warehouse:
if not item.rejected_warehouse:
frappe.throw(
_("Row #{0}: Rejected Warehouse is mandatory against rejected Item {1}").format(
d.idx, d.item_code
_("Row #{0}: Rejected Warehouse is mandatory for the rejected Item {1}").format(
item.idx, item.item_code
)
)
if item.get("rejected_warehouse") and (item.get("rejected_warehouse") == item.get("warehouse")):
frappe.throw(
_("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx)
)
# validate accepted and rejected qty
def validate_accepted_rejected_qty(self):
for d in self.get("items"):

View File

@@ -669,7 +669,11 @@ def get_filters(
if reference_voucher_detail_no:
filters["voucher_detail_no"] = reference_voucher_detail_no
if item_row and item_row.get("warehouse"):
if (
voucher_type in ["Purchase Receipt", "Purchase Invoice"]
and item_row
and item_row.get("warehouse")
):
filters["warehouse"] = item_row.get("warehouse")
return filters

View File

@@ -201,6 +201,12 @@ class StockController(AccountsController):
warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"]
expense_account = frappe.get_cached_value("Company", self.company, "default_expense_account")
if not expense_account:
frappe.throw(
_(
"Please set default cost of goods sold account in company {0} for booking rounding gain and loss during stock transfer"
).format(frappe.bold(self.company))
)
gl_list.append(
self.get_gl_dict(

View File

@@ -30,11 +30,6 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
var me = this;
let doc = this.frm.doc;
erpnext.toggle_naming_series();
frappe.dynamic_link = {
doc: doc,
fieldname: 'name',
doctype: 'Lead'
};
if (!this.frm.is_new() && doc.__onload && !doc.__onload.is_customer) {
this.frm.add_custom_button(__("Customer"), this.make_customer, __("Create"));

View File

@@ -3,8 +3,6 @@
frappe.ui.form.on('Prospect', {
refresh (frm) {
frappe.dynamic_link = { doc: frm.doc, fieldname: "name", doctype: frm.doctype };
if (!frm.is_new() && frappe.boot.user.can_create.includes("Customer")) {
frm.add_custom_button(__("Customer"), function() {
frappe.model.open_mapped_doc({

View File

@@ -415,6 +415,10 @@ scheduler_events = {
"erpnext.selling.doctype.quotation.quotation.set_expired_status",
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email",
"erpnext.accounts.utils.auto_create_exchange_rate_revaluation_daily",
],
"weekly": [
"erpnext.accounts.utils.auto_create_exchange_rate_revaluation_weekly",
],
"daily_long": [
"erpnext.setup.doctype.email_digest.email_digest.send",

View File

@@ -131,8 +131,6 @@ frappe.ui.form.on("Customer", {
erpnext.toggle_naming_series();
}
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Customer'}
if(!frm.doc.__islocal) {
frappe.contacts.render_address_and_contact(frm);

View File

@@ -81,8 +81,6 @@ frappe.ui.form.on("Company", {
disbale_coa_fields(frm);
frappe.contacts.render_address_and_contact(frm);
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Company'}
if (frappe.perm.has_perm("Cost Center", 0, 'read')) {
frm.add_custom_button(__('Cost Centers'), function() {
frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name});

View File

@@ -95,6 +95,10 @@
"depreciation_cost_center",
"capital_work_in_progress_account",
"asset_received_but_not_billed",
"exchange_rate_revaluation_settings_section",
"auto_exchange_rate_revaluation",
"auto_err_frequency",
"submit_err_jv",
"budget_detail",
"exception_budget_approver_role",
"registration_info",
@@ -731,6 +735,29 @@
"fieldname": "book_advance_payments_in_separate_party_account",
"fieldtype": "Check",
"label": "Book Advance Payments in Separate Party Account"
},
{
"fieldname": "exchange_rate_revaluation_settings_section",
"fieldtype": "Section Break",
"label": "Exchange Rate Revaluation Settings"
},
{
"default": "0",
"fieldname": "auto_exchange_rate_revaluation",
"fieldtype": "Check",
"label": "Auto Create Exchange Rate Revaluation"
},
{
"fieldname": "auto_err_frequency",
"fieldtype": "Select",
"label": "Frequency",
"options": "Daily\nWeekly"
},
{
"default": "0",
"fieldname": "submit_err_jv",
"fieldtype": "Check",
"label": "Submit ERR Journals?"
}
],
"icon": "fa fa-building",
@@ -738,7 +765,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
"modified": "2023-06-23 18:22:27.219706",
"modified": "2023-07-07 05:41:41.537256",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",

View File

@@ -3,8 +3,6 @@
frappe.ui.form.on('Sales Partner', {
refresh: function(frm) {
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Sales Partner'}
if(frm.doc.__islocal){
hide_field(['address_html', 'contact_html', 'address_contacts']);
frappe.contacts.clear_address_and_contact(frm);

View File

@@ -47,8 +47,6 @@ frappe.ui.form.on('Batch', {
return;
}
debugger
const section = frm.dashboard.add_section('', __("Stock Levels"));
// sort by qty

View File

@@ -59,6 +59,73 @@ class TestBatch(FrappeTestCase):
return receipt
def test_batch_stock_levels(self, batch_qty=100):
"""Test automated batch creation from Purchase Receipt"""
self.make_batch_item("ITEM-BATCH-1")
receipt = frappe.get_doc(
dict(
doctype="Purchase Receipt",
supplier="_Test Supplier",
company="_Test Company",
items=[dict(item_code="ITEM-BATCH-1", qty=10, rate=10, warehouse="Stores - _TC")],
)
).insert()
receipt.submit()
receipt.load_from_db()
batch_no = get_batch_from_bundle(receipt.items[0].serial_and_batch_bundle)
bundle_id = (
SerialBatchCreation(
{
"item_code": "ITEM-BATCH-1",
"warehouse": "_Test Warehouse - _TC",
"actual_qty": 20,
"voucher_type": "Purchase Receipt",
"batches": frappe._dict({batch_no: 20}),
"type_of_transaction": "Inward",
"company": receipt.company,
}
)
.make_serial_and_batch_bundle()
.name
)
receipt2 = frappe.get_doc(
dict(
doctype="Purchase Receipt",
supplier="_Test Supplier",
company="_Test Company",
items=[
dict(
item_code="ITEM-BATCH-1",
qty=20,
rate=10,
warehouse="_Test Warehouse - _TC",
serial_and_batch_bundle=bundle_id,
)
],
)
).insert()
receipt2.submit()
receipt.load_from_db()
receipt2.load_from_db()
self.assertTrue(receipt.items[0].serial_and_batch_bundle)
self.assertTrue(receipt2.items[0].serial_and_batch_bundle)
batchwise_qty = frappe._dict({})
for receipt in [receipt, receipt2]:
batch_no = get_batch_from_bundle(receipt.items[0].serial_and_batch_bundle)
key = (batch_no, receipt.items[0].warehouse)
batchwise_qty[key] = receipt.items[0].qty
batches = get_batch_qty(batch_no)
for d in batches:
self.assertEqual(d.qty, batchwise_qty[(d.batch_no, d.warehouse)])
def test_stock_entry_incoming(self):
"""Test batch creation via Stock Entry (Work Order)"""

View File

@@ -318,6 +318,37 @@ class TestDeliveryNote(FrappeTestCase):
self.assertEqual(dn.per_returned, 100)
self.assertEqual(dn.status, "Return Issued")
def test_delivery_note_return_valuation_on_different_warehuose(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
item_code = "Test Return Valuation For DN"
make_item("Test Return Valuation For DN", {"is_stock_item": 1})
return_warehouse = create_warehouse("Returned Test Warehouse", company=company)
make_stock_entry(item_code=item_code, target="Stores - TCP1", qty=5, basic_rate=150)
dn = create_delivery_note(
item_code=item_code,
qty=5,
rate=500,
warehouse="Stores - TCP1",
company=company,
expense_account="Cost of Goods Sold - TCP1",
cost_center="Main - TCP1",
)
dn.submit()
self.assertEqual(dn.items[0].incoming_rate, 150)
from erpnext.controllers.sales_and_purchase_return import make_return_doc
return_dn = make_return_doc(dn.doctype, dn.name)
return_dn.items[0].warehouse = return_warehouse
return_dn.save().submit()
self.assertEqual(return_dn.items[0].incoming_rate, 150)
def test_return_single_item_from_bundled_items(self):
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")

View File

@@ -3,7 +3,6 @@
frappe.ui.form.on('Manufacturer', {
refresh: function(frm) {
frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Manufacturer' };
if (frm.doc.__islocal) {
hide_field(['address_html','contact_html']);
frappe.contacts.clear_address_and_contact(frm);

View File

@@ -438,6 +438,7 @@
{
"fieldname": "rejected_warehouse",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Rejected Warehouse",
"no_copy": 1,
"oldfieldname": "rejected_warehouse",
@@ -1240,7 +1241,7 @@
"idx": 261,
"is_submittable": 1,
"links": [],
"modified": "2023-06-03 16:23:20.781368",
"modified": "2023-07-04 17:23:17.025390",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",

View File

@@ -350,6 +350,15 @@ class TestPurchaseReceipt(FrappeTestCase):
pr.cancel()
self.assertFalse(frappe.db.get_value("Serial No", pr_row_1_serial_no, "warehouse"))
def test_rejected_warehouse_filter(self):
pr = frappe.copy_doc(test_records[0])
pr.get("items")[0].item_code = "_Test Serialized Item With Series"
pr.get("items")[0].qty = 3
pr.get("items")[0].rejected_qty = 2
pr.get("items")[0].received_qty = 5
pr.get("items")[0].rejected_warehouse = pr.get("items")[0].warehouse
self.assertRaises(frappe.ValidationError, pr.save)
def test_rejected_serial_no(self):
pr = frappe.copy_doc(test_records[0])
pr.get("items")[0].item_code = "_Test Serialized Item With Series"

View File

@@ -502,6 +502,7 @@
{
"fieldname": "rejected_warehouse",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Rejected Warehouse",
"no_copy": 1,
"oldfieldname": "rejected_warehouse",
@@ -1058,7 +1059,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2023-07-02 18:40:48.152637",
"modified": "2023-07-04 17:22:02.830029",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",

View File

@@ -1272,24 +1272,29 @@ def get_reserved_batches_for_pos(kwargs):
if ids:
for d in get_serial_batch_ledgers(kwargs.item_code, docstatus=1, name=ids):
if d.batch_no not in pos_batches:
pos_batches[d.batch_no] = frappe._dict(
key = (d.batch_no, d.warehouse)
if key not in pos_batches:
pos_batches[key] = frappe._dict(
{
"qty": d.qty,
"warehouse": d.warehouse,
}
)
else:
pos_batches[d.batch_no].qty += d.qty
pos_batches[key].qty += d.qty
for row in pos_invoices:
if not row.batch_no:
continue
if row.batch_no in pos_batches:
pos_batches[row.batch_no] -= row.qty * -1 if row.is_return else row.qty
if kwargs.get("batch_no") and row.batch_no != kwargs.get("batch_no"):
continue
key = (row.batch_no, row.warehouse)
if key in pos_batches:
pos_batches[key] -= row.qty * -1 if row.is_return else row.qty
else:
pos_batches[row.batch_no] = frappe._dict(
pos_batches[key] = frappe._dict(
{
"qty": (row.qty * -1 if row.is_return else row.qty),
"warehouse": row.warehouse,
@@ -1309,6 +1314,7 @@ def get_auto_batch_nos(kwargs):
update_available_batches(available_batches, stock_ledgers_batches, pos_invoice_batches)
available_batches = list(filter(lambda x: x.qty > 0, available_batches))
if not qty:
return available_batches
@@ -1351,10 +1357,11 @@ def get_qty_based_available_batches(available_batches, qty):
def update_available_batches(available_batches, reserved_batches=None, pos_invoice_batches=None):
for batches in [reserved_batches, pos_invoice_batches]:
if batches:
for batch_no, data in batches.items():
for key, data in batches.items():
batch_no, warehouse = key
batch_not_exists = True
for batch in available_batches:
if batch.batch_no == batch_no and batch.warehouse == data.warehouse:
if batch.batch_no == batch_no and batch.warehouse == warehouse:
batch.qty += data.qty
batch_not_exists = False
@@ -1563,7 +1570,7 @@ def get_stock_ledgers_batches(kwargs):
.groupby(stock_ledger_entry.batch_no, stock_ledger_entry.warehouse)
)
for field in ["warehouse", "item_code"]:
for field in ["warehouse", "item_code", "batch_no"]:
if not kwargs.get(field):
continue
@@ -1582,6 +1589,10 @@ def get_stock_ledgers_batches(kwargs):
data = query.run(as_dict=True)
batches = {}
for d in data:
batches[d.batch_no] = d
key = (d.batch_no, d.warehouse)
if key not in batches:
batches[key] = d
else:
batches[key].qty += d.qty
return batches

View File

@@ -83,12 +83,6 @@ frappe.ui.form.on("Warehouse", {
}
frm.toggle_enable(["is_group", "company"], false);
frappe.dynamic_link = {
doc: frm.doc,
fieldname: "name",
doctype: "Warehouse",
};
},
});

View File

@@ -251,6 +251,7 @@
"description": "Sets 'Rejected Warehouse' in each row of the Items table.",
"fieldname": "rejected_warehouse",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Rejected Warehouse",
"no_copy": 1,
"options": "Warehouse",
@@ -630,7 +631,7 @@
"in_create": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-06-03 16:18:39.088518",
"modified": "2023-07-06 18:43:16.171842",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt",

View File

@@ -192,13 +192,23 @@ class SubcontractingReceipt(SubcontractingController):
self.total = total_amount
def validate_rejected_warehouse(self):
if not self.rejected_warehouse:
for item in self.items:
if item.rejected_qty:
for item in self.items:
if flt(item.rejected_qty) and not item.rejected_warehouse:
if self.rejected_warehouse:
item.rejected_warehouse = self.rejected_warehouse
if not item.rejected_warehouse:
frappe.throw(
_("Rejected Warehouse is mandatory against rejected Item {0}").format(item.item_code)
_("Row #{0}: Rejected Warehouse is mandatory for the rejected Item {1}").format(
item.idx, item.item_code
)
)
if item.get("rejected_warehouse") and (item.get("rejected_warehouse") == item.get("warehouse")):
frappe.throw(
_("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx)
)
def validate_available_qty_for_consumption(self):
for item in self.get("supplied_items"):
precision = item.precision("consumed_qty")

View File

@@ -254,6 +254,7 @@
"depends_on": "eval: !parent.is_return",
"fieldname": "rejected_warehouse",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Rejected Warehouse",
"no_copy": 1,
"options": "Warehouse",
@@ -494,7 +495,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2023-03-12 14:00:41.418681",
"modified": "2023-07-06 18:43:45.599761",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt Item",