mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-09 08:11:19 +00:00
22
.github/workflows/initiate_release.yml
vendored
22
.github/workflows/initiate_release.yml
vendored
@@ -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 }}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 - (
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = ""
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -47,8 +47,6 @@ frappe.ui.form.on('Batch', {
|
||||
return;
|
||||
}
|
||||
|
||||
debugger
|
||||
|
||||
const section = frm.dashboard.add_section('', __("Stock Levels"));
|
||||
|
||||
// sort by qty
|
||||
|
||||
@@ -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)"""
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user