mirror of
https://github.com/frappe/erpnext.git
synced 2026-03-21 16:02:15 +00:00
Merge pull request #42541 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -73,7 +73,9 @@
|
||||
"remarks_section",
|
||||
"general_ledger_remarks_length",
|
||||
"column_break_lvjk",
|
||||
"receivable_payable_remarks_length"
|
||||
"receivable_payable_remarks_length",
|
||||
"payment_request_settings",
|
||||
"create_pr_in_draft_status"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -475,6 +477,18 @@
|
||||
"fieldname": "calculate_depr_using_total_days",
|
||||
"fieldtype": "Check",
|
||||
"label": "Calculate daily depreciation using total days in depreciation period"
|
||||
},
|
||||
{
|
||||
"description": "Payment Request created from Sales Order or Purchase Order will be in Draft status. When disabled document will be in unsaved state.",
|
||||
"fieldname": "payment_request_settings",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Payment Request"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "create_pr_in_draft_status",
|
||||
"fieldtype": "Check",
|
||||
"label": "Create in Draft Status"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@@ -482,7 +496,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2024-07-12 00:24:20.957726",
|
||||
"modified": "2024-07-26 06:48:52.714630",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -35,6 +35,7 @@ class AccountsSettings(Document):
|
||||
book_tax_discount_loss: DF.Check
|
||||
calculate_depr_using_total_days: DF.Check
|
||||
check_supplier_invoice_uniqueness: DF.Check
|
||||
create_pr_in_draft_status: DF.Check
|
||||
credit_controller: DF.Link | None
|
||||
delete_linked_ledger_entries: DF.Check
|
||||
determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"]
|
||||
|
||||
@@ -25,30 +25,6 @@ frappe.ui.form.on("Journal Entry", {
|
||||
refresh: function (frm) {
|
||||
erpnext.toggle_naming_series();
|
||||
|
||||
if (frm.doc.repost_required && frm.doc.docstatus === 1) {
|
||||
frm.set_intro(
|
||||
__(
|
||||
"Accounting entries for this Journal Entry need to be reposted. Please click on 'Repost' button to update."
|
||||
)
|
||||
);
|
||||
frm.add_custom_button(__("Repost Accounting Entries"), () => {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
method: "repost_accounting_entries",
|
||||
freeze: true,
|
||||
freeze_message: __("Reposting..."),
|
||||
callback: (r) => {
|
||||
if (!r.exc) {
|
||||
frappe.msgprint(__("Accounting Entries are reposted."));
|
||||
frm.refresh();
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
.removeClass("btn-default")
|
||||
.addClass("btn-warning");
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus > 0) {
|
||||
frm.add_custom_button(
|
||||
__("Ledger"),
|
||||
|
||||
@@ -64,8 +64,7 @@
|
||||
"stock_entry",
|
||||
"subscription_section",
|
||||
"auto_repeat",
|
||||
"amended_from",
|
||||
"repost_required"
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -544,15 +543,6 @@
|
||||
"label": "Is System Generated",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "repost_required",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Repost Required",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@@ -567,7 +557,7 @@
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2023-11-23 12:11:04.128015",
|
||||
"modified": "2024-07-18 15:32:29.413598",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry",
|
||||
@@ -618,4 +608,4 @@
|
||||
"states": [],
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,9 +47,7 @@ class JournalEntry(AccountsController):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.accounts.doctype.journal_entry_account.journal_entry_account import (
|
||||
JournalEntryAccount,
|
||||
)
|
||||
from erpnext.accounts.doctype.journal_entry_account.journal_entry_account import JournalEntryAccount
|
||||
|
||||
accounts: DF.Table[JournalEntryAccount]
|
||||
amended_from: DF.Link | None
|
||||
@@ -197,14 +195,10 @@ class JournalEntry(AccountsController):
|
||||
self.update_booked_depreciation()
|
||||
|
||||
def on_update_after_submit(self):
|
||||
if hasattr(self, "repost_required"):
|
||||
self.needs_repost = self.check_if_fields_updated(
|
||||
fields_to_check=[], child_tables={"accounts": []}
|
||||
)
|
||||
if self.needs_repost:
|
||||
self.validate_for_repost()
|
||||
self.db_set("repost_required", self.needs_repost)
|
||||
self.repost_accounting_entries()
|
||||
self.needs_repost = self.check_if_fields_updated(fields_to_check=[], child_tables={"accounts": []})
|
||||
if self.needs_repost:
|
||||
self.validate_for_repost()
|
||||
self.repost_accounting_entries()
|
||||
|
||||
def on_cancel(self):
|
||||
# References for this Journal are removed on the `on_cancel` event in accounts_controller
|
||||
|
||||
@@ -36,7 +36,7 @@ frappe.ui.form.on("Payment Order", {
|
||||
|
||||
// payment Entry
|
||||
if (frm.doc.docstatus === 1 && frm.doc.payment_order_type === "Payment Request") {
|
||||
frm.add_custom_button(__("Create Payment Entries"), function () {
|
||||
frm.add_custom_button(__("Create Journal Entries"), function () {
|
||||
frm.trigger("make_payment_records");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -500,7 +500,8 @@ def make_payment_request(**args):
|
||||
if args.order_type == "Shopping Cart" or args.mute_email:
|
||||
pr.flags.mute_email = True
|
||||
|
||||
pr.insert(ignore_permissions=True)
|
||||
if frappe.db.get_single_value("Accounts Settings", "create_pr_in_draft_status", cache=True):
|
||||
pr.insert(ignore_permissions=True)
|
||||
if args.submit_doc:
|
||||
pr.submit()
|
||||
|
||||
|
||||
@@ -77,31 +77,6 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
erpnext.accounts.ledger_preview.show_stock_ledger_preview(this.frm);
|
||||
}
|
||||
|
||||
if (this.frm.doc.repost_required && this.frm.doc.docstatus === 1) {
|
||||
this.frm.set_intro(
|
||||
__(
|
||||
"Accounting entries for this invoice need to be reposted. Please click on 'Repost' button to update."
|
||||
)
|
||||
);
|
||||
this.frm
|
||||
.add_custom_button(__("Repost Accounting Entries"), () => {
|
||||
this.frm.call({
|
||||
doc: this.frm.doc,
|
||||
method: "repost_accounting_entries",
|
||||
freeze: true,
|
||||
freeze_message: __("Reposting..."),
|
||||
callback: (r) => {
|
||||
if (!r.exc) {
|
||||
frappe.msgprint(__("Accounting Entries are reposted."));
|
||||
me.frm.refresh();
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
.removeClass("btn-default")
|
||||
.addClass("btn-warning");
|
||||
}
|
||||
|
||||
if (!doc.is_return && doc.docstatus == 1 && doc.outstanding_amount != 0) {
|
||||
if (doc.on_hold) {
|
||||
this.frm.add_custom_button(
|
||||
|
||||
@@ -170,7 +170,6 @@
|
||||
"against_expense_account",
|
||||
"column_break_63",
|
||||
"unrealized_profit_loss_account",
|
||||
"repost_required",
|
||||
"subscription_section",
|
||||
"subscription",
|
||||
"auto_repeat",
|
||||
@@ -364,7 +363,8 @@
|
||||
"description": "Once set, this invoice will be on hold till the set date",
|
||||
"fieldname": "release_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Release Date"
|
||||
"label": "Release Date",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "cb_17",
|
||||
@@ -1603,15 +1603,6 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Company Default Round Off Cost Center"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "repost_required",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Repost Required",
|
||||
"options": "Account",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_transaction_date_exchange_rate",
|
||||
@@ -1639,7 +1630,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-04-11 11:28:42.802211",
|
||||
"modified": "2024-07-25 19:42:36.931278",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -159,7 +159,6 @@ class PurchaseInvoice(BuyingController):
|
||||
rejected_warehouse: DF.Link | None
|
||||
release_date: DF.Date | None
|
||||
remarks: DF.SmallText | None
|
||||
repost_required: DF.Check
|
||||
represents_company: DF.Link | None
|
||||
return_against: DF.Link | None
|
||||
rounded_total: DF.Currency
|
||||
@@ -796,19 +795,17 @@ class PurchaseInvoice(BuyingController):
|
||||
self.process_common_party_accounting()
|
||||
|
||||
def on_update_after_submit(self):
|
||||
if hasattr(self, "repost_required"):
|
||||
fields_to_check = [
|
||||
"cash_bank_account",
|
||||
"write_off_account",
|
||||
"unrealized_profit_loss_account",
|
||||
"is_opening",
|
||||
]
|
||||
child_tables = {"items": ("expense_account",), "taxes": ("account_head",)}
|
||||
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
|
||||
if self.needs_repost:
|
||||
self.validate_for_repost()
|
||||
self.db_set("repost_required", self.needs_repost)
|
||||
self.repost_accounting_entries()
|
||||
fields_to_check = [
|
||||
"cash_bank_account",
|
||||
"write_off_account",
|
||||
"unrealized_profit_loss_account",
|
||||
"is_opening",
|
||||
]
|
||||
child_tables = {"items": ("expense_account",), "taxes": ("account_head",)}
|
||||
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
|
||||
if self.needs_repost:
|
||||
self.validate_for_repost()
|
||||
self.repost_accounting_entries()
|
||||
|
||||
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||
update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
|
||||
@@ -1705,6 +1702,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.db_set("release_date", None)
|
||||
|
||||
def set_tax_withholding(self):
|
||||
self.set("advance_tax", [])
|
||||
self.set("tax_withheld_vouchers", [])
|
||||
|
||||
if not self.apply_tds:
|
||||
return
|
||||
|
||||
@@ -1746,8 +1746,6 @@ class PurchaseInvoice(BuyingController):
|
||||
self.remove(d)
|
||||
|
||||
## Add pending vouchers on which tax was withheld
|
||||
self.set("tax_withheld_vouchers", [])
|
||||
|
||||
for voucher_no, voucher_details in voucher_wise_amount.items():
|
||||
self.append(
|
||||
"tax_withheld_vouchers",
|
||||
@@ -1762,7 +1760,6 @@ class PurchaseInvoice(BuyingController):
|
||||
self.calculate_taxes_and_totals()
|
||||
|
||||
def allocate_advance_tds(self, tax_withholding_details, advance_taxes):
|
||||
self.set("advance_tax", [])
|
||||
for tax in advance_taxes:
|
||||
allocated_amount = 0
|
||||
pending_amount = flt(tax.tax_amount - tax.allocated_amount)
|
||||
|
||||
@@ -2014,8 +2014,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
["Service - _TC", 1000, 0.0, nowdate()],
|
||||
]
|
||||
check_gl_entries(self, pi.name, expected_gle, nowdate())
|
||||
pi.load_from_db()
|
||||
self.assertFalse(pi.repost_required)
|
||||
|
||||
@change_settings("Buying Settings", {"supplier_group": None})
|
||||
def test_purchase_invoice_without_supplier_group(self):
|
||||
|
||||
@@ -68,31 +68,6 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
|
||||
this.frm.toggle_reqd("due_date", !this.frm.doc.is_return);
|
||||
|
||||
if (this.frm.doc.repost_required && this.frm.doc.docstatus === 1) {
|
||||
this.frm.set_intro(
|
||||
__(
|
||||
"Accounting entries for this invoice needs to be reposted. Please click on 'Repost' button to update."
|
||||
)
|
||||
);
|
||||
this.frm
|
||||
.add_custom_button(__("Repost Accounting Entries"), () => {
|
||||
this.frm.call({
|
||||
doc: this.frm.doc,
|
||||
method: "repost_accounting_entries",
|
||||
freeze: true,
|
||||
freeze_message: __("Reposting..."),
|
||||
callback: (r) => {
|
||||
if (!r.exc) {
|
||||
frappe.msgprint(__("Accounting Entries are reposted"));
|
||||
me.frm.refresh();
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
.removeClass("btn-default")
|
||||
.addClass("btn-warning");
|
||||
}
|
||||
|
||||
if (this.frm.doc.is_return) {
|
||||
this.frm.return_print_format = "Sales Invoice Return";
|
||||
}
|
||||
@@ -502,11 +477,12 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
frappe.msgprint(__("Please specify Company to proceed"));
|
||||
} else {
|
||||
var me = this;
|
||||
const for_validate = me.frm.doc.is_return ? true : false;
|
||||
return this.frm.call({
|
||||
doc: me.frm.doc,
|
||||
method: "set_missing_values",
|
||||
args: {
|
||||
for_validate: true,
|
||||
for_validate: for_validate,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
|
||||
@@ -213,7 +213,6 @@
|
||||
"is_internal_customer",
|
||||
"is_discounted",
|
||||
"remarks",
|
||||
"repost_required",
|
||||
"connections_tab"
|
||||
],
|
||||
"fields": [
|
||||
@@ -2125,15 +2124,6 @@
|
||||
"label": "Write Off",
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "repost_required",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Repost Required",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "incoterm",
|
||||
"fieldtype": "Link",
|
||||
@@ -2188,7 +2178,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2024-06-07 16:49:32.458402",
|
||||
"modified": "2024-07-18 15:30:39.428519",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -161,7 +161,6 @@ class SalesInvoice(SellingController):
|
||||
project: DF.Link | None
|
||||
redeem_loyalty_points: DF.Check
|
||||
remarks: DF.SmallText | None
|
||||
repost_required: DF.Check
|
||||
represents_company: DF.Link | None
|
||||
return_against: DF.Link | None
|
||||
rounded_total: DF.Currency
|
||||
@@ -556,7 +555,6 @@ class SalesInvoice(SellingController):
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
self.db_set("status", "Cancelled")
|
||||
self.db_set("repost_required", 0)
|
||||
|
||||
if frappe.db.get_single_value("Selling Settings", "sales_update_frequency") == "Each Transaction":
|
||||
update_company_current_month_sales(self.company)
|
||||
@@ -706,25 +704,23 @@ class SalesInvoice(SellingController):
|
||||
data.sales_invoice = sales_invoice
|
||||
|
||||
def on_update_after_submit(self):
|
||||
if hasattr(self, "repost_required"):
|
||||
fields_to_check = [
|
||||
"additional_discount_account",
|
||||
"cash_bank_account",
|
||||
"account_for_change_amount",
|
||||
"write_off_account",
|
||||
"loyalty_redemption_account",
|
||||
"unrealized_profit_loss_account",
|
||||
"is_opening",
|
||||
]
|
||||
child_tables = {
|
||||
"items": ("income_account", "expense_account", "discount_account"),
|
||||
"taxes": ("account_head",),
|
||||
}
|
||||
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
|
||||
if self.needs_repost:
|
||||
self.validate_for_repost()
|
||||
self.db_set("repost_required", self.needs_repost)
|
||||
self.repost_accounting_entries()
|
||||
fields_to_check = [
|
||||
"additional_discount_account",
|
||||
"cash_bank_account",
|
||||
"account_for_change_amount",
|
||||
"write_off_account",
|
||||
"loyalty_redemption_account",
|
||||
"unrealized_profit_loss_account",
|
||||
"is_opening",
|
||||
]
|
||||
child_tables = {
|
||||
"items": ("income_account", "expense_account", "discount_account"),
|
||||
"taxes": ("account_head",),
|
||||
}
|
||||
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
|
||||
if self.needs_repost:
|
||||
self.validate_for_repost()
|
||||
self.repost_accounting_entries()
|
||||
|
||||
def set_paid_amount(self):
|
||||
paid_amount = 0.0
|
||||
|
||||
@@ -2952,9 +2952,6 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
|
||||
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
|
||||
|
||||
si.load_from_db()
|
||||
self.assertFalse(si.repost_required)
|
||||
|
||||
def test_asset_depreciation_on_sale_with_pro_rata(self):
|
||||
"""
|
||||
Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on its date of sale.
|
||||
|
||||
@@ -268,6 +268,11 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
||||
vouchers, voucher_wise_amount = get_invoice_vouchers(
|
||||
parties, tax_details, inv.company, party_type=party_type
|
||||
)
|
||||
|
||||
payment_entry_vouchers = get_payment_entry_vouchers(
|
||||
parties, tax_details, inv.company, party_type=party_type
|
||||
)
|
||||
|
||||
advance_vouchers = get_advance_vouchers(
|
||||
parties,
|
||||
company=inv.company,
|
||||
@@ -275,7 +280,8 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
||||
to_date=tax_details.to_date,
|
||||
party_type=party_type,
|
||||
)
|
||||
taxable_vouchers = vouchers + advance_vouchers
|
||||
|
||||
taxable_vouchers = vouchers + advance_vouchers + payment_entry_vouchers
|
||||
tax_deducted_on_advances = 0
|
||||
|
||||
if inv.doctype == "Purchase Invoice":
|
||||
@@ -387,6 +393,20 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
|
||||
return vouchers, voucher_wise_amount
|
||||
|
||||
|
||||
def get_payment_entry_vouchers(parties, tax_details, company, party_type="Supplier"):
|
||||
payment_entry_filters = {
|
||||
"party_type": party_type,
|
||||
"party": ("in", parties),
|
||||
"docstatus": 1,
|
||||
"apply_tax_withholding_amount": 1,
|
||||
"posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
|
||||
"tax_withholding_category": tax_details.get("tax_withholding_category"),
|
||||
"company": company,
|
||||
}
|
||||
|
||||
return frappe.db.get_all("Payment Entry", filters=payment_entry_filters, pluck="name")
|
||||
|
||||
|
||||
def get_advance_vouchers(parties, company=None, from_date=None, to_date=None, party_type="Supplier"):
|
||||
"""
|
||||
Use Payment Ledger to fetch unallocated Advance Payments
|
||||
|
||||
@@ -139,6 +139,7 @@ class ReceivablePayableReport:
|
||||
paid_in_account_currency=0.0,
|
||||
credit_note_in_account_currency=0.0,
|
||||
outstanding_in_account_currency=0.0,
|
||||
cost_center=ple.cost_center,
|
||||
)
|
||||
self.get_invoices(ple)
|
||||
|
||||
@@ -253,7 +254,7 @@ class ReceivablePayableReport:
|
||||
row.paid -= amount
|
||||
row.paid_in_account_currency -= amount_in_account_currency
|
||||
|
||||
if ple.cost_center:
|
||||
if not row.cost_center and ple.cost_center:
|
||||
row.cost_center = str(ple.cost_center)
|
||||
|
||||
def update_sub_total_row(self, row, party):
|
||||
|
||||
@@ -53,11 +53,13 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
si = si.submit()
|
||||
return si
|
||||
|
||||
def create_payment_entry(self, docname):
|
||||
def create_payment_entry(self, docname, do_not_submit=False):
|
||||
pe = get_payment_entry("Sales Invoice", docname, bank_account=self.cash, party_amount=40)
|
||||
pe.paid_from = self.debit_to
|
||||
pe.insert()
|
||||
pe.submit()
|
||||
if not do_not_submit:
|
||||
pe.submit()
|
||||
return pe
|
||||
|
||||
def create_credit_note(self, docname, do_not_submit=False):
|
||||
credit_note = create_sales_invoice(
|
||||
@@ -984,3 +986,40 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
expected_data_after_payment,
|
||||
[row.invoice_grand_total, row.invoiced, row.paid, row.outstanding],
|
||||
)
|
||||
|
||||
def test_cost_center_on_report_output(self):
|
||||
filters = {
|
||||
"company": self.company,
|
||||
"report_date": today(),
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
}
|
||||
|
||||
# check invoice grand total and invoiced column's value for 3 payment terms
|
||||
si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||
si.cost_center = self.cost_center
|
||||
si.save().submit()
|
||||
|
||||
new_cc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Cost Center",
|
||||
"cost_center_name": "East Wing",
|
||||
"parent_cost_center": self.company + " - " + self.company_abbr,
|
||||
"company": self.company,
|
||||
}
|
||||
)
|
||||
new_cc.save()
|
||||
|
||||
# check invoice grand total, invoiced, paid and outstanding column's value after payment
|
||||
pe = self.create_payment_entry(si.name, do_not_submit=True)
|
||||
pe.cost_center = new_cc.name
|
||||
pe.save().submit()
|
||||
report = execute(filters)
|
||||
|
||||
expected_data_after_payment = [si.name, si.cost_center, 60]
|
||||
|
||||
self.assertEqual(len(report[1]), 1)
|
||||
row = report[1][0]
|
||||
self.assertEqual(expected_data_after_payment, [row.voucher_no, row.cost_center, row.outstanding])
|
||||
|
||||
@@ -315,8 +315,9 @@ def apply_conditions(query, pi, pii, filters):
|
||||
|
||||
|
||||
def get_items(filters, additional_table_columns):
|
||||
pi = frappe.qb.DocType("Purchase Invoice")
|
||||
pii = frappe.qb.DocType("Purchase Invoice Item")
|
||||
doctype = "Purchase Invoice"
|
||||
pi = frappe.qb.DocType(doctype)
|
||||
pii = frappe.qb.DocType(f"{doctype} Item")
|
||||
Item = frappe.qb.DocType("Item")
|
||||
query = (
|
||||
frappe.qb.from_(pi)
|
||||
@@ -353,6 +354,7 @@ def get_items(filters, additional_table_columns):
|
||||
pi.mode_of_payment,
|
||||
)
|
||||
.where(pi.docstatus == 1)
|
||||
.where(pii.parenttype == doctype)
|
||||
)
|
||||
|
||||
if filters.get("supplier"):
|
||||
|
||||
@@ -410,8 +410,9 @@ def apply_group_by_conditions(query, si, ii, filters):
|
||||
|
||||
|
||||
def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||
si = frappe.qb.DocType("Sales Invoice")
|
||||
sii = frappe.qb.DocType("Sales Invoice Item")
|
||||
doctype = "Sales Invoice"
|
||||
si = frappe.qb.DocType(doctype)
|
||||
sii = frappe.qb.DocType(f"{doctype} Item")
|
||||
item = frappe.qb.DocType("Item")
|
||||
|
||||
query = (
|
||||
@@ -459,6 +460,7 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||
sii.qty,
|
||||
)
|
||||
.where(si.docstatus == 1)
|
||||
.where(sii.parenttype == doctype)
|
||||
)
|
||||
|
||||
if additional_query_columns:
|
||||
|
||||
@@ -1598,6 +1598,18 @@ def auto_create_exchange_rate_revaluation_weekly() -> None:
|
||||
create_err_and_its_journals(companies)
|
||||
|
||||
|
||||
def auto_create_exchange_rate_revaluation_monthly() -> None:
|
||||
"""
|
||||
Executed by background job
|
||||
"""
|
||||
companies = frappe.db.get_all(
|
||||
"Company",
|
||||
filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Montly"},
|
||||
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:
|
||||
|
||||
@@ -740,7 +740,7 @@ class TestDepreciationMethods(AssetSetup):
|
||||
available_for_use_date="2030-06-06",
|
||||
is_existing_asset=1,
|
||||
opening_number_of_booked_depreciations=2,
|
||||
opening_accumulated_depreciation=47095.89,
|
||||
opening_accumulated_depreciation=47178.08,
|
||||
expected_value_after_useful_life=10000,
|
||||
depreciation_start_date="2032-12-31",
|
||||
total_number_of_depreciations=3,
|
||||
@@ -748,7 +748,7 @@ class TestDepreciationMethods(AssetSetup):
|
||||
)
|
||||
|
||||
self.assertEqual(asset.status, "Draft")
|
||||
expected_schedules = [["2032-12-31", 42904.11, 90000.0]]
|
||||
expected_schedules = [["2032-12-31", 30000.0, 77178.08], ["2033-06-06", 12821.92, 90000.0]]
|
||||
schedules = [
|
||||
[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
|
||||
for d in get_depr_schedule(asset.name, "Draft")
|
||||
|
||||
@@ -552,9 +552,18 @@ def _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=False):
|
||||
# if not existing asset, from_date = available_for_use_date
|
||||
# otherwise, if opening_number_of_booked_depreciations = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12
|
||||
# from_date = 01/01/2022
|
||||
from_date = _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly=False)
|
||||
days = date_diff(row.depreciation_start_date, from_date) + 1
|
||||
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
|
||||
if row.depreciation_method in ("Straight Line", "Manual"):
|
||||
prev_depreciation_start_date = add_months(
|
||||
row.depreciation_start_date,
|
||||
(row.frequency_of_depreciation * -1) * asset_doc.opening_number_of_booked_depreciations,
|
||||
)
|
||||
from_date = asset_doc.available_for_use_date
|
||||
days = date_diff(prev_depreciation_start_date, from_date) + 1
|
||||
total_days = get_total_days(prev_depreciation_start_date, row.frequency_of_depreciation)
|
||||
else:
|
||||
from_date = _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly=False)
|
||||
days = date_diff(row.depreciation_start_date, from_date) + 1
|
||||
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
|
||||
if days <= 0:
|
||||
frappe.throw(
|
||||
_(
|
||||
@@ -682,20 +691,15 @@ def get_straight_line_or_manual_depr_amount(
|
||||
# if the Depreciation Schedule is being prepared for the first time
|
||||
else:
|
||||
if row.daily_prorata_based:
|
||||
amount = (
|
||||
flt(asset.gross_purchase_amount)
|
||||
- flt(asset.opening_accumulated_depreciation)
|
||||
- flt(row.expected_value_after_useful_life)
|
||||
)
|
||||
amount = flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
|
||||
return get_daily_prorata_based_straight_line_depr(
|
||||
asset, row, schedule_idx, number_of_pending_depreciations, amount
|
||||
)
|
||||
else:
|
||||
return (
|
||||
flt(asset.gross_purchase_amount)
|
||||
- flt(asset.opening_accumulated_depreciation)
|
||||
- flt(row.expected_value_after_useful_life)
|
||||
) / flt(row.total_number_of_depreciations - asset.opening_number_of_booked_depreciations)
|
||||
depreciation_amount = (
|
||||
flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
|
||||
) / flt(row.total_number_of_depreciations)
|
||||
return depreciation_amount
|
||||
|
||||
|
||||
def get_daily_prorata_based_straight_line_depr(
|
||||
@@ -725,7 +729,11 @@ def get_daily_depr_amount(asset, row, schedule_idx, amount):
|
||||
)
|
||||
),
|
||||
add_days(
|
||||
get_last_day(add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)),
|
||||
add_months(
|
||||
row.depreciation_start_date,
|
||||
(row.frequency_of_depreciation * (asset.opening_number_of_booked_depreciations + 1))
|
||||
* -1,
|
||||
),
|
||||
1,
|
||||
),
|
||||
)
|
||||
@@ -904,7 +912,7 @@ def _get_daily_prorata_based_default_wdv_or_dd_depr_amount(
|
||||
|
||||
|
||||
def get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value):
|
||||
""" "
|
||||
"""
|
||||
Returns monthly depreciation amount when year changes
|
||||
1. Calculate per day depr based on new year
|
||||
2. Calculate monthly amount based on new per day amount
|
||||
|
||||
@@ -75,6 +75,116 @@ class TestAssetDepreciationSchedule(FrappeTestCase):
|
||||
]
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
def test_schedule_for_slm_for_existing_asset_daily_pro_rata_enabled(self):
|
||||
frappe.db.set_single_value("Accounts Settings", "calculate_depr_using_total_days", 1)
|
||||
asset = create_asset(
|
||||
calculate_depreciation=1,
|
||||
depreciation_method="Straight Line",
|
||||
available_for_use_date="2023-10-10",
|
||||
is_existing_asset=1,
|
||||
opening_number_of_booked_depreciations=9,
|
||||
opening_accumulated_depreciation=265,
|
||||
depreciation_start_date="2024-07-31",
|
||||
total_number_of_depreciations=24,
|
||||
frequency_of_depreciation=1,
|
||||
gross_purchase_amount=731,
|
||||
daily_prorata_based=1,
|
||||
)
|
||||
|
||||
expected_schedules = [
|
||||
["2024-07-31", 31.0, 296.0],
|
||||
["2024-08-31", 31.0, 327.0],
|
||||
["2024-09-30", 30.0, 357.0],
|
||||
["2024-10-31", 31.0, 388.0],
|
||||
["2024-11-30", 30.0, 418.0],
|
||||
["2024-12-31", 31.0, 449.0],
|
||||
["2025-01-31", 31.0, 480.0],
|
||||
["2025-02-28", 28.0, 508.0],
|
||||
["2025-03-31", 31.0, 539.0],
|
||||
["2025-04-30", 30.0, 569.0],
|
||||
["2025-05-31", 31.0, 600.0],
|
||||
["2025-06-30", 30.0, 630.0],
|
||||
["2025-07-31", 31.0, 661.0],
|
||||
["2025-08-31", 31.0, 692.0],
|
||||
["2025-09-30", 30.0, 722.0],
|
||||
["2025-10-10", 9.0, 731.0],
|
||||
]
|
||||
schedules = [
|
||||
[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
|
||||
for d in get_depr_schedule(asset.name, "Draft")
|
||||
]
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
frappe.db.set_single_value("Accounts Settings", "calculate_depr_using_total_days", 0)
|
||||
|
||||
def test_schedule_for_slm_for_existing_asset(self):
|
||||
asset = create_asset(
|
||||
calculate_depreciation=1,
|
||||
depreciation_method="Straight Line",
|
||||
available_for_use_date="2023-10-10",
|
||||
is_existing_asset=1,
|
||||
opening_number_of_booked_depreciations=9,
|
||||
opening_accumulated_depreciation=265.30,
|
||||
depreciation_start_date="2024-07-31",
|
||||
total_number_of_depreciations=24,
|
||||
frequency_of_depreciation=1,
|
||||
gross_purchase_amount=731,
|
||||
)
|
||||
|
||||
expected_schedules = [
|
||||
["2024-07-31", 30.46, 295.76],
|
||||
["2024-08-31", 30.46, 326.22],
|
||||
["2024-09-30", 30.46, 356.68],
|
||||
["2024-10-31", 30.46, 387.14],
|
||||
["2024-11-30", 30.46, 417.6],
|
||||
["2024-12-31", 30.46, 448.06],
|
||||
["2025-01-31", 30.46, 478.52],
|
||||
["2025-02-28", 30.46, 508.98],
|
||||
["2025-03-31", 30.46, 539.44],
|
||||
["2025-04-30", 30.46, 569.9],
|
||||
["2025-05-31", 30.46, 600.36],
|
||||
["2025-06-30", 30.46, 630.82],
|
||||
["2025-07-31", 30.46, 661.28],
|
||||
["2025-08-31", 30.46, 691.74],
|
||||
["2025-09-30", 30.46, 722.2],
|
||||
["2025-10-10", 8.8, 731.0],
|
||||
]
|
||||
schedules = [
|
||||
[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
|
||||
for d in get_depr_schedule(asset.name, "Draft")
|
||||
]
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
def test_schedule_sl_method_for_existing_asset_with_frequency_of_3_months(self):
|
||||
asset = create_asset(
|
||||
calculate_depreciation=1,
|
||||
depreciation_method="Straight Line",
|
||||
available_for_use_date="2023-11-01",
|
||||
is_existing_asset=1,
|
||||
opening_number_of_booked_depreciations=4,
|
||||
opening_accumulated_depreciation=223.15,
|
||||
depreciation_start_date="2024-12-31",
|
||||
total_number_of_depreciations=12,
|
||||
frequency_of_depreciation=3,
|
||||
gross_purchase_amount=731,
|
||||
)
|
||||
|
||||
expected_schedules = [
|
||||
["2024-12-31", 60.92, 284.07],
|
||||
["2025-03-31", 60.92, 344.99],
|
||||
["2025-06-30", 60.92, 405.91],
|
||||
["2025-09-30", 60.92, 466.83],
|
||||
["2025-12-31", 60.92, 527.75],
|
||||
["2026-03-31", 60.92, 588.67],
|
||||
["2026-06-30", 60.92, 649.59],
|
||||
["2026-09-30", 60.92, 710.51],
|
||||
["2026-11-01", 20.49, 731.0],
|
||||
]
|
||||
schedules = [
|
||||
[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
|
||||
for d in get_depr_schedule(asset.name, "Draft")
|
||||
]
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
# Enable Checkbox to Calculate depreciation using total days in depreciation period
|
||||
def test_daily_prorata_based_depr_after_enabling_configuration(self):
|
||||
frappe.db.set_single_value("Accounts Settings", "calculate_depr_using_total_days", 1)
|
||||
|
||||
@@ -39,16 +39,14 @@ def validate_filters(filters):
|
||||
def get_data(filters):
|
||||
po = frappe.qb.DocType("Purchase Order")
|
||||
po_item = frappe.qb.DocType("Purchase Order Item")
|
||||
pi = frappe.qb.DocType("Purchase Invoice")
|
||||
pi_item = frappe.qb.DocType("Purchase Invoice Item")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(po)
|
||||
.from_(po_item)
|
||||
.inner_join(po_item)
|
||||
.on(po_item.parent == po.name)
|
||||
.left_join(pi_item)
|
||||
.on(pi_item.po_detail == po_item.name & pi_item.docstatus == 1)
|
||||
.left_join(pi)
|
||||
.on(pi.name == pi_item.parent & pi.docstatus == 1)
|
||||
.on((pi_item.po_detail == po_item.name) & (pi_item.docstatus == 1))
|
||||
.select(
|
||||
po.transaction_date.as_("date"),
|
||||
po_item.schedule_date.as_("required_date"),
|
||||
|
||||
@@ -2489,16 +2489,12 @@ class AccountsController(TransactionBase):
|
||||
|
||||
@frappe.whitelist()
|
||||
def repost_accounting_entries(self):
|
||||
if self.repost_required:
|
||||
repost_ledger = frappe.new_doc("Repost Accounting Ledger")
|
||||
repost_ledger.company = self.company
|
||||
repost_ledger.append("vouchers", {"voucher_type": self.doctype, "voucher_no": self.name})
|
||||
repost_ledger.flags.ignore_permissions = True
|
||||
repost_ledger.insert()
|
||||
repost_ledger.submit()
|
||||
self.db_set("repost_required", 0)
|
||||
else:
|
||||
frappe.throw(_("No updates pending for reposting"))
|
||||
repost_ledger = frappe.new_doc("Repost Accounting Ledger")
|
||||
repost_ledger.company = self.company
|
||||
repost_ledger.append("vouchers", {"voucher_type": self.doctype, "voucher_no": self.name})
|
||||
repost_ledger.flags.ignore_permissions = True
|
||||
repost_ledger.insert()
|
||||
repost_ledger.submit()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -640,6 +640,12 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
|
||||
def update_terms(source_doc, target_doc, source_parent):
|
||||
target_doc.payment_amount = -source_doc.payment_amount
|
||||
|
||||
def item_condition(doc):
|
||||
if return_against_rejected_qty:
|
||||
return doc.rejected_qty
|
||||
|
||||
return doc.qty
|
||||
|
||||
doclist = get_mapped_doc(
|
||||
doctype,
|
||||
source_name,
|
||||
@@ -654,6 +660,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
|
||||
"doctype": doctype + " Item",
|
||||
"field_map": {"serial_no": "serial_no", "batch_no": "batch_no", "bom": "bom"},
|
||||
"postprocess": update_item,
|
||||
"condition": item_condition,
|
||||
},
|
||||
"Payment Schedule": {"doctype": "Payment Schedule", "postprocess": update_terms},
|
||||
},
|
||||
|
||||
@@ -456,6 +456,7 @@ scheduler_events = {
|
||||
],
|
||||
"monthly_long": [
|
||||
"erpnext.accounts.deferred_revenue.process_deferred_accounting",
|
||||
"erpnext.accounts.utils.auto_create_exchange_rate_revaluation_monthly",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -365,6 +365,12 @@ def get_children(doctype=None, parent=None, **kwargs):
|
||||
return frappe.get_all("BOM Creator Item", fields=fields, filters=query_filters, order_by="idx")
|
||||
|
||||
|
||||
def get_parent_row_no(doc, name):
|
||||
for row in doc.items:
|
||||
if row.name == name:
|
||||
return row.idx
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_item(**kwargs):
|
||||
if isinstance(kwargs, str):
|
||||
@@ -375,6 +381,11 @@ def add_item(**kwargs):
|
||||
|
||||
doc = frappe.get_doc("BOM Creator", kwargs.parent)
|
||||
item_info = get_item_details(kwargs.item_code)
|
||||
|
||||
parent_row_no = ""
|
||||
if kwargs.fg_reference_id and doc.name != kwargs.fg_reference_id:
|
||||
parent_row_no = get_parent_row_no(doc, kwargs.fg_reference_id)
|
||||
|
||||
kwargs.update(
|
||||
{
|
||||
"uom": item_info.stock_uom,
|
||||
@@ -383,6 +394,9 @@ def add_item(**kwargs):
|
||||
}
|
||||
)
|
||||
|
||||
if parent_row_no:
|
||||
kwargs.update({"parent_row_no": parent_row_no})
|
||||
|
||||
doc.append("items", kwargs)
|
||||
doc.save()
|
||||
|
||||
|
||||
@@ -1781,6 +1781,12 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
apply_price_list(item, reset_plc_conversion) {
|
||||
// We need to reset plc_conversion_rate sometimes because the call to
|
||||
// `erpnext.stock.get_item_details.apply_price_list` is sensitive to its value
|
||||
|
||||
|
||||
if (this.frm.doc.doctype === "Material Request") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!reset_plc_conversion) {
|
||||
this.frm.set_value("plc_conversion_rate", "");
|
||||
}
|
||||
|
||||
@@ -754,7 +754,7 @@
|
||||
"fieldname": "auto_err_frequency",
|
||||
"fieldtype": "Select",
|
||||
"label": "Frequency",
|
||||
"options": "Daily\nWeekly"
|
||||
"options": "Daily\nWeekly\nMonthly"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -808,7 +808,7 @@
|
||||
"image_field": "company_logo",
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2024-06-21 17:46:25.567565",
|
||||
"modified": "2024-07-24 18:17:56.413971",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Company",
|
||||
|
||||
@@ -31,7 +31,7 @@ class Company(NestedSet):
|
||||
accumulated_depreciation_account: DF.Link | None
|
||||
allow_account_creation_against_child_company: DF.Check
|
||||
asset_received_but_not_billed: DF.Link | None
|
||||
auto_err_frequency: DF.Literal["Daily", "Weekly"]
|
||||
auto_err_frequency: DF.Literal["Daily", "Weekly", "Monthly"]
|
||||
auto_exchange_rate_revaluation: DF.Check
|
||||
book_advance_payments_in_separate_party_account: DF.Check
|
||||
capital_work_in_progress_account: DF.Link | None
|
||||
|
||||
@@ -163,7 +163,7 @@ def make_taxes_and_charges_template(company_name, doctype, template):
|
||||
doc.flags.ignore_links = True
|
||||
doc.flags.ignore_validate = True
|
||||
doc.flags.ignore_mandatory = True
|
||||
doc.insert(ignore_permissions=True)
|
||||
doc.insert(ignore_permissions=True, ignore_if_duplicate=True)
|
||||
return doc
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ def make_item_tax_template(company_name, template):
|
||||
# Ingone validations to make doctypes faster
|
||||
doc.flags.ignore_links = True
|
||||
doc.flags.ignore_validate = True
|
||||
doc.insert(ignore_permissions=True)
|
||||
doc.insert(ignore_permissions=True, ignore_if_duplicate=True)
|
||||
return doc
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ def get_or_create_account(company_name, account):
|
||||
doc = frappe.get_doc(account)
|
||||
doc.flags.ignore_links = True
|
||||
doc.flags.ignore_validate = True
|
||||
doc.insert(ignore_permissions=True, ignore_mandatory=True)
|
||||
doc.insert(ignore_permissions=True, ignore_mandatory=True, ignore_if_duplicate=True)
|
||||
return doc
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import json
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import add_days, cstr, flt, nowdate, nowtime, today
|
||||
from frappe.utils import add_days, cstr, flt, getdate, nowdate, nowtime, today
|
||||
|
||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||
from erpnext.accounts.utils import get_balance_on
|
||||
@@ -2005,6 +2005,40 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
|
||||
self.assertRaises(frappe.ValidationError, dn5.submit)
|
||||
|
||||
def test_warranty_expiry_date_for_serial_item(self):
|
||||
item_code = make_item(
|
||||
"Test Warranty Expiry Date Item",
|
||||
properties={
|
||||
"has_serial_no": 1,
|
||||
"serial_no_series": "TWE.#####",
|
||||
"is_stock_item": 1,
|
||||
"warranty_period": 100,
|
||||
},
|
||||
).name
|
||||
|
||||
se = make_stock_entry(
|
||||
item_code=item_code,
|
||||
target="_Test Warehouse - _TC",
|
||||
qty=2,
|
||||
basic_rate=50,
|
||||
posting_date=nowdate(),
|
||||
)
|
||||
|
||||
serial_nos = get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle)
|
||||
create_delivery_note(
|
||||
item_code=item_code,
|
||||
qty=2,
|
||||
rate=300,
|
||||
use_serial_batch_fields=0,
|
||||
serial_no=serial_nos,
|
||||
)
|
||||
|
||||
for row in serial_nos:
|
||||
sn = frappe.get_doc("Serial No", row)
|
||||
self.assertEqual(getdate(sn.warranty_expiry_date), getdate(add_days(nowdate(), 100)))
|
||||
self.assertEqual(sn.status, "Delivered")
|
||||
self.assertEqual(sn.warranty_period, 100)
|
||||
|
||||
|
||||
def create_delivery_note(**args):
|
||||
dn = frappe.new_doc("Delivery Note")
|
||||
|
||||
@@ -1886,9 +1886,19 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
rate=100,
|
||||
rejected_qty=2,
|
||||
rejected_warehouse=rejected_warehouse,
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pr.append(
|
||||
"items",
|
||||
{"item_code": item_code, "qty": 2, "rate": 100, "warehouse": warehouse, "rejected_qty": 0},
|
||||
)
|
||||
pr.save()
|
||||
pr.submit()
|
||||
self.assertEqual(len(pr.items), 2)
|
||||
|
||||
pr_return = make_purchase_return_against_rejected_warehouse(pr.name)
|
||||
self.assertEqual(len(pr_return.items), 1)
|
||||
self.assertEqual(pr_return.items[0].warehouse, rejected_warehouse)
|
||||
self.assertEqual(pr_return.items[0].qty, 2.0 * -1)
|
||||
self.assertEqual(pr_return.items[0].rejected_qty, 0.0)
|
||||
|
||||
@@ -92,8 +92,10 @@ class SerialandBatchBundle(Document):
|
||||
if self.type_of_transaction == "Maintenance":
|
||||
return
|
||||
|
||||
self.validate_serial_nos_duplicate()
|
||||
self.check_future_entries_exists()
|
||||
if not self.flags.ignore_validate_serial_batch or frappe.flags.in_test:
|
||||
self.validate_serial_nos_duplicate()
|
||||
self.check_future_entries_exists()
|
||||
|
||||
self.set_is_outward()
|
||||
self.calculate_total_qty()
|
||||
self.set_warehouse()
|
||||
@@ -340,6 +342,9 @@ class SerialandBatchBundle(Document):
|
||||
rate = frappe.db.get_value(child_table, self.voucher_detail_no, valuation_field)
|
||||
|
||||
for d in self.entries:
|
||||
if (d.incoming_rate == rate) and d.qty and d.stock_value_difference:
|
||||
continue
|
||||
|
||||
d.incoming_rate = flt(rate, precision)
|
||||
if d.qty:
|
||||
d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate)
|
||||
@@ -393,32 +398,6 @@ class SerialandBatchBundle(Document):
|
||||
|
||||
self.calculate_qty_and_amount(save=True)
|
||||
self.validate_quantity(row, qty_field=qty_field)
|
||||
self.set_warranty_expiry_date()
|
||||
|
||||
def set_warranty_expiry_date(self):
|
||||
if self.type_of_transaction != "Outward":
|
||||
return
|
||||
|
||||
if not (self.docstatus == 1 and self.voucher_type == "Delivery Note" and self.has_serial_no):
|
||||
return
|
||||
|
||||
warranty_period = frappe.get_cached_value("Item", self.item_code, "warranty_period")
|
||||
|
||||
if not warranty_period:
|
||||
return
|
||||
|
||||
warranty_expiry_date = add_days(self.posting_date, cint(warranty_period))
|
||||
|
||||
serial_nos = self.get_serial_nos()
|
||||
if not serial_nos:
|
||||
return
|
||||
|
||||
sn_table = frappe.qb.DocType("Serial No")
|
||||
(
|
||||
frappe.qb.update(sn_table)
|
||||
.set(sn_table.warranty_expiry_date, warranty_expiry_date)
|
||||
.where(sn_table.name.isin(serial_nos))
|
||||
).run()
|
||||
|
||||
def validate_voucher_no(self):
|
||||
if not (self.voucher_type and self.voucher_no):
|
||||
@@ -867,6 +846,9 @@ class SerialandBatchBundle(Document):
|
||||
self.validate_serial_nos_inventory()
|
||||
|
||||
def set_purchase_document_no(self):
|
||||
if self.flags.ignore_validate_serial_batch:
|
||||
return
|
||||
|
||||
if not self.has_serial_no:
|
||||
return
|
||||
|
||||
|
||||
@@ -83,7 +83,8 @@
|
||||
"job_card",
|
||||
"amended_from",
|
||||
"credit_note",
|
||||
"is_return"
|
||||
"is_return",
|
||||
"tab_connections"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -683,6 +684,12 @@
|
||||
"label": "Asset Repair",
|
||||
"options": "Asset Repair",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "tab_connections",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Connections",
|
||||
"show_dashboard": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
|
||||
26
erpnext/stock/doctype/stock_entry/stock_entry_dashboard.py
Normal file
26
erpnext/stock/doctype/stock_entry/stock_entry_dashboard.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from frappe import _
|
||||
|
||||
|
||||
# Todo: non_standard_fieldnames is to be decided
|
||||
def get_data():
|
||||
return {
|
||||
"fieldname": "stock_entry",
|
||||
"non_standard_fieldnames": {
|
||||
# "DocType Name": "Reference field name",
|
||||
},
|
||||
"internal_links": {
|
||||
"Purchase Order": ["items", "purchase_order"],
|
||||
"Subcontracting Order": ["items", "subcontracting_order"],
|
||||
"Subcontracting Receipt": ["items", "subcontracting_receipt"],
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"label": _("Reference"),
|
||||
"items": [
|
||||
"Purchase Order",
|
||||
"Subcontracting Order",
|
||||
"Subcontracting Receipt",
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -434,7 +434,6 @@
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "use_serial_batch_fields",
|
||||
"description": "If enabled, do not update serial / batch values in the stock transactions on creation of auto Serial \n / Batch Bundle. ",
|
||||
"fieldname": "do_not_update_serial_batch_on_creation_of_auto_bundle",
|
||||
"fieldtype": "Check",
|
||||
@@ -460,7 +459,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2024-07-15 17:18:23.872161",
|
||||
"modified": "2024-07-29 14:55:19.093508",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Settings",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
|
||||
frappe.query_reports["Product Bundle Balance"] = {
|
||||
filters: [
|
||||
{
|
||||
fieldname: "company",
|
||||
label: __("Company"),
|
||||
fieldtype: "Link",
|
||||
options: "Company",
|
||||
default: frappe.defaults.get_user_default("Company"),
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "date",
|
||||
label: __("Date"),
|
||||
|
||||
@@ -224,6 +224,9 @@ def get_stock_ledger_entries(filters, items):
|
||||
.where((sle2.name.isnull()) & (sle.docstatus < 2) & (sle.item_code.isin(items)))
|
||||
)
|
||||
|
||||
if filters.get("company"):
|
||||
query = query.where(sle.company == filters.get("company"))
|
||||
|
||||
if date := filters.get("date"):
|
||||
query = query.where(sle.posting_date <= date)
|
||||
else:
|
||||
@@ -237,7 +240,7 @@ def get_stock_ledger_entries(filters, items):
|
||||
if warehouse_details:
|
||||
wh = frappe.qb.DocType("Warehouse")
|
||||
query = query.where(
|
||||
ExistsCriterion(
|
||||
sle.warehouse.isin(
|
||||
frappe.qb.from_(wh)
|
||||
.select(wh.name)
|
||||
.where((wh.lft >= warehouse_details.lft) & (wh.rgt <= warehouse_details.rgt))
|
||||
|
||||
@@ -4,7 +4,7 @@ import frappe
|
||||
from frappe import _, bold
|
||||
from frappe.model.naming import make_autoname
|
||||
from frappe.query_builder.functions import CombineDatetime, Sum, Timestamp
|
||||
from frappe.utils import cint, cstr, flt, get_link_to_form, now, nowtime, today
|
||||
from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, now, nowtime, today
|
||||
from pypika import Order
|
||||
|
||||
from erpnext.stock.deprecated_serial_batch import (
|
||||
@@ -110,6 +110,7 @@ class SerialBatchBundle:
|
||||
"type_of_transaction": "Inward" if self.sle.actual_qty > 0 else "Outward",
|
||||
"company": self.company,
|
||||
"is_rejected": self.is_rejected_entry(),
|
||||
"make_bundle_from_sle": 1,
|
||||
}
|
||||
).make_serial_and_batch_bundle()
|
||||
|
||||
@@ -160,12 +161,13 @@ class SerialBatchBundle:
|
||||
|
||||
if msg:
|
||||
error_msg = (
|
||||
f"Serial and Batch Bundle not set for item {self.item_code} in warehouse {self.warehouse}."
|
||||
f"Serial and Batch Bundle not set for item {self.item_code} in warehouse {self.warehouse}"
|
||||
+ msg
|
||||
)
|
||||
frappe.throw(_(error_msg))
|
||||
|
||||
def set_serial_and_batch_bundle(self, sn_doc):
|
||||
self.sle.auto_created_serial_and_batch_bundle = 1
|
||||
self.sle.db_set({"serial_and_batch_bundle": sn_doc.name, "auto_created_serial_and_batch_bundle": 1})
|
||||
|
||||
if sn_doc.is_rejected:
|
||||
@@ -324,6 +326,9 @@ class SerialBatchBundle:
|
||||
def set_warehouse_and_status_in_serial_nos(self):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos as get_parsed_serial_nos
|
||||
|
||||
if self.sle.auto_created_serial_and_batch_bundle and self.sle.actual_qty > 0:
|
||||
return
|
||||
|
||||
serial_nos = get_serial_nos(self.sle.serial_and_batch_bundle)
|
||||
if not self.sle.serial_and_batch_bundle and self.sle.serial_no:
|
||||
serial_nos = get_parsed_serial_nos(self.sle.serial_no)
|
||||
@@ -338,7 +343,8 @@ class SerialBatchBundle:
|
||||
status = "Delivered"
|
||||
|
||||
sn_table = frappe.qb.DocType("Serial No")
|
||||
(
|
||||
|
||||
query = (
|
||||
frappe.qb.update(sn_table)
|
||||
.set(sn_table.warehouse, warehouse)
|
||||
.set(
|
||||
@@ -351,7 +357,19 @@ class SerialBatchBundle:
|
||||
)
|
||||
.set(sn_table.company, self.sle.company)
|
||||
.where(sn_table.name.isin(serial_nos))
|
||||
).run()
|
||||
)
|
||||
|
||||
if status == "Delivered":
|
||||
warranty_period = frappe.get_cached_value("Item", self.sle.item_code, "warranty_period")
|
||||
if warranty_period:
|
||||
warranty_expiry_date = add_days(self.sle.posting_date, cint(warranty_period))
|
||||
query = query.set(sn_table.warranty_expiry_date, warranty_expiry_date)
|
||||
query = query.set(sn_table.warranty_period, warranty_period)
|
||||
else:
|
||||
query = query.set(sn_table.warranty_expiry_date, None)
|
||||
query = query.set(sn_table.warranty_period, 0)
|
||||
|
||||
query.run()
|
||||
|
||||
def set_batch_no_in_serial_nos(self):
|
||||
entries = frappe.get_all(
|
||||
@@ -915,6 +933,10 @@ class SerialBatchCreation:
|
||||
if doc.voucher_no and frappe.get_cached_value(doc.voucher_type, doc.voucher_no, "docstatus") == 2:
|
||||
doc.voucher_no = ""
|
||||
|
||||
doc.flags.ignore_validate_serial_batch = False
|
||||
if self.get("make_bundle_from_sle") and self.type_of_transaction == "Inward":
|
||||
doc.flags.ignore_validate_serial_batch = True
|
||||
|
||||
doc.save()
|
||||
self.validate_qty(doc)
|
||||
|
||||
@@ -1107,6 +1129,10 @@ class SerialBatchCreation:
|
||||
msg = f"Please set Serial No Series in the item {self.item_code} or create Serial and Batch Bundle manually."
|
||||
frappe.throw(_(msg))
|
||||
|
||||
voucher_no = ""
|
||||
if self.get("voucher_no"):
|
||||
voucher_no = self.get("voucher_no")
|
||||
|
||||
for _i in range(abs(cint(self.actual_qty))):
|
||||
serial_no = make_autoname(self.serial_no_series, "Serial No")
|
||||
sr_nos.append(serial_no)
|
||||
@@ -1124,6 +1150,7 @@ class SerialBatchCreation:
|
||||
self.item_name,
|
||||
self.description,
|
||||
"Active",
|
||||
voucher_no,
|
||||
self.batch_no,
|
||||
)
|
||||
)
|
||||
@@ -1142,6 +1169,7 @@ class SerialBatchCreation:
|
||||
"item_name",
|
||||
"description",
|
||||
"status",
|
||||
"purchase_document_no",
|
||||
"batch_no",
|
||||
]
|
||||
|
||||
|
||||
@@ -275,7 +275,9 @@ def repost_future_sle(
|
||||
)
|
||||
affected_transactions.update(obj.affected_transactions)
|
||||
|
||||
distinct_item_warehouses[(args[i].get("item_code"), args[i].get("warehouse"))].reposting_status = True
|
||||
key = (args[i].get("item_code"), args[i].get("warehouse"))
|
||||
if distinct_item_warehouses.get(key):
|
||||
distinct_item_warehouses[key].reposting_status = True
|
||||
|
||||
if obj.new_items_found:
|
||||
for _item_wh, data in distinct_item_warehouses.items():
|
||||
@@ -1588,9 +1590,11 @@ def get_stock_ledger_entries(
|
||||
if not previous_sle.get("posting_date"):
|
||||
previous_sle["posting_datetime"] = "1900-01-01 00:00:00"
|
||||
else:
|
||||
previous_sle["posting_datetime"] = get_combine_datetime(
|
||||
previous_sle["posting_date"], previous_sle["posting_time"]
|
||||
)
|
||||
posting_time = previous_sle.get("posting_time")
|
||||
if not posting_time:
|
||||
posting_time = "00:00:00"
|
||||
|
||||
previous_sle["posting_datetime"] = get_combine_datetime(previous_sle["posting_date"], posting_time)
|
||||
|
||||
if operator in (">", "<=") and previous_sle.get("name"):
|
||||
conditions += " and name!=%(name)s"
|
||||
|
||||
@@ -23,18 +23,6 @@
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project",
|
||||
"address_and_contact_section",
|
||||
"supplier_address",
|
||||
"address_display",
|
||||
"contact_person",
|
||||
"contact_display",
|
||||
"contact_mobile",
|
||||
"contact_email",
|
||||
"column_break_19",
|
||||
"shipping_address",
|
||||
"shipping_address_display",
|
||||
"billing_address",
|
||||
"billing_address_display",
|
||||
"section_break_24",
|
||||
"column_break_25",
|
||||
"set_warehouse",
|
||||
@@ -48,10 +36,23 @@
|
||||
"raw_materials_supplied_section",
|
||||
"set_reserve_warehouse",
|
||||
"supplied_items",
|
||||
"additional_costs_section",
|
||||
"tab_address_and_contact",
|
||||
"supplier_address",
|
||||
"address_display",
|
||||
"contact_person",
|
||||
"contact_display",
|
||||
"contact_mobile",
|
||||
"contact_email",
|
||||
"column_break_19",
|
||||
"shipping_address",
|
||||
"shipping_address_display",
|
||||
"billing_address",
|
||||
"billing_address_display",
|
||||
"tab_additional_costs",
|
||||
"distribute_additional_costs_based_on",
|
||||
"additional_costs",
|
||||
"total_additional_costs",
|
||||
"tab_other_info",
|
||||
"order_status_section",
|
||||
"status",
|
||||
"column_break_39",
|
||||
@@ -59,7 +60,8 @@
|
||||
"printing_settings_section",
|
||||
"select_print_heading",
|
||||
"column_break_43",
|
||||
"letter_head"
|
||||
"letter_head",
|
||||
"tab_connections"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -95,7 +97,7 @@
|
||||
"fieldtype": "Link",
|
||||
"in_global_search": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Supplier",
|
||||
"label": "Job Worker",
|
||||
"options": "Supplier",
|
||||
"print_hide": 1,
|
||||
"reqd": 1,
|
||||
@@ -107,7 +109,7 @@
|
||||
"fieldname": "supplier_name",
|
||||
"fieldtype": "Data",
|
||||
"in_global_search": 1,
|
||||
"label": "Supplier Name",
|
||||
"label": "Job Worker Name",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
@@ -115,7 +117,7 @@
|
||||
"depends_on": "supplier",
|
||||
"fieldname": "supplier_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Supplier Warehouse",
|
||||
"label": "Job Worker Warehouse",
|
||||
"options": "Warehouse",
|
||||
"reqd": 1
|
||||
},
|
||||
@@ -166,9 +168,8 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "address_and_contact_section",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldname": "tab_address_and_contact",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Address and Contact"
|
||||
},
|
||||
{
|
||||
@@ -176,14 +177,14 @@
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "supplier_address",
|
||||
"fieldtype": "Link",
|
||||
"label": "Supplier Address",
|
||||
"label": "Job Worker Address",
|
||||
"options": "Address",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "address_display",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Supplier Address Details",
|
||||
"label": "Job Worker Address Details",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -191,7 +192,7 @@
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "contact_person",
|
||||
"fieldtype": "Link",
|
||||
"label": "Supplier Contact",
|
||||
"label": "Job Worker Contact",
|
||||
"options": "Contact",
|
||||
"print_hide": 1
|
||||
},
|
||||
@@ -337,11 +338,9 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "total_additional_costs",
|
||||
"depends_on": "eval:(doc.docstatus == 0 || doc.total_additional_costs)",
|
||||
"fieldname": "additional_costs_section",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldname": "tab_additional_costs",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Additional Costs"
|
||||
},
|
||||
{
|
||||
@@ -449,6 +448,17 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "tab_other_info",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Other Info"
|
||||
},
|
||||
{
|
||||
"fieldname": "tab_connections",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Connections",
|
||||
"show_dashboard": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
|
||||
@@ -23,18 +23,6 @@
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project",
|
||||
"section_addresses",
|
||||
"supplier_address",
|
||||
"contact_person",
|
||||
"address_display",
|
||||
"contact_display",
|
||||
"contact_mobile",
|
||||
"contact_email",
|
||||
"col_break_address",
|
||||
"shipping_address",
|
||||
"shipping_address_display",
|
||||
"billing_address",
|
||||
"billing_address_display",
|
||||
"sec_warehouse",
|
||||
"set_warehouse",
|
||||
"rejected_warehouse",
|
||||
@@ -53,23 +41,36 @@
|
||||
"get_current_stock",
|
||||
"raw_material_details",
|
||||
"supplied_items",
|
||||
"additional_costs_section",
|
||||
"distribute_additional_costs_based_on",
|
||||
"additional_costs",
|
||||
"total_additional_costs",
|
||||
"section_break_46",
|
||||
"in_words",
|
||||
"bill_no",
|
||||
"bill_date",
|
||||
"tab_addresses",
|
||||
"supplier_address",
|
||||
"contact_person",
|
||||
"address_display",
|
||||
"contact_display",
|
||||
"contact_mobile",
|
||||
"contact_email",
|
||||
"col_break_address",
|
||||
"shipping_address",
|
||||
"shipping_address_display",
|
||||
"billing_address",
|
||||
"billing_address_display",
|
||||
"tab_additional_costs",
|
||||
"distribute_additional_costs_based_on",
|
||||
"additional_costs",
|
||||
"total_additional_costs",
|
||||
"tab_other_info",
|
||||
"more_info",
|
||||
"status",
|
||||
"column_break_39",
|
||||
"per_returned",
|
||||
"section_break_47",
|
||||
"amended_from",
|
||||
"range",
|
||||
"column_break4",
|
||||
"represents_company",
|
||||
"order_status_section",
|
||||
"status",
|
||||
"column_break_39",
|
||||
"per_returned",
|
||||
"subscription_detail",
|
||||
"auto_repeat",
|
||||
"printing_settings",
|
||||
@@ -84,7 +85,8 @@
|
||||
"transporter_name",
|
||||
"column_break5",
|
||||
"lr_no",
|
||||
"lr_date"
|
||||
"lr_date",
|
||||
"tab_connections"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -112,7 +114,7 @@
|
||||
"fieldname": "supplier",
|
||||
"fieldtype": "Link",
|
||||
"in_global_search": 1,
|
||||
"label": "Supplier",
|
||||
"label": "Job Worker",
|
||||
"options": "Supplier",
|
||||
"print_hide": 1,
|
||||
"print_width": "150px",
|
||||
@@ -127,7 +129,7 @@
|
||||
"fieldname": "supplier_name",
|
||||
"fieldtype": "Data",
|
||||
"in_global_search": 1,
|
||||
"label": "Supplier Name",
|
||||
"label": "Job Worker Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -174,15 +176,14 @@
|
||||
"width": "150px"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_addresses",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldname": "tab_addresses",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Address and Contact"
|
||||
},
|
||||
{
|
||||
"fieldname": "supplier_address",
|
||||
"fieldtype": "Link",
|
||||
"label": "Select Supplier Address",
|
||||
"label": "Select Job Worker Address",
|
||||
"options": "Address",
|
||||
"print_hide": 1
|
||||
},
|
||||
@@ -269,7 +270,7 @@
|
||||
{
|
||||
"fieldname": "supplier_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Supplier Warehouse",
|
||||
"label": "Job Worker Warehouse",
|
||||
"no_copy": 1,
|
||||
"options": "Warehouse",
|
||||
"print_hide": 1,
|
||||
@@ -414,6 +415,7 @@
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "subscription_detail",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Auto Repeat Detail"
|
||||
@@ -571,10 +573,6 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_47",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
@@ -598,11 +596,9 @@
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "total_additional_costs",
|
||||
"depends_on": "eval:(doc.docstatus == 0 || doc.total_additional_costs)",
|
||||
"fieldname": "additional_costs_section",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldname": "tab_additional_costs",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Additional Costs"
|
||||
},
|
||||
{
|
||||
@@ -643,7 +639,7 @@
|
||||
{
|
||||
"fieldname": "supplier_delivery_note",
|
||||
"fieldtype": "Data",
|
||||
"label": "Supplier Delivery Note"
|
||||
"label": "Job Worker Delivery Note"
|
||||
},
|
||||
{
|
||||
"fieldname": "raw_materials_consumed_section",
|
||||
@@ -658,6 +654,23 @@
|
||||
{
|
||||
"fieldname": "column_break_uinr",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "tab_other_info",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Other Info"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "order_status_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Order Status"
|
||||
},
|
||||
{
|
||||
"fieldname": "tab_connections",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Connections",
|
||||
"show_dashboard": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
|
||||
Reference in New Issue
Block a user