Merge branch 'develop' into fixed-process-loss-in-job-card

This commit is contained in:
rohitwaghchaure 2023-06-12 19:04:19 +05:30 committed by GitHub
commit 93fe923e2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 1236 additions and 1530 deletions

View File

@ -50,13 +50,15 @@ class AccountingDimension(Document):
if frappe.flags.in_test:
make_dimension_in_accounting_doctypes(doc=self)
else:
frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self, queue="long")
frappe.enqueue(
make_dimension_in_accounting_doctypes, doc=self, queue="long", enqueue_after_commit=True
)
def on_trash(self):
if frappe.flags.in_test:
delete_accounting_dimension(doc=self)
else:
frappe.enqueue(delete_accounting_dimension, doc=self, queue="long")
frappe.enqueue(delete_accounting_dimension, doc=self, queue="long", enqueue_after_commit=True)
def set_fieldname_and_label(self):
if not self.label:

View File

@ -41,7 +41,7 @@ frappe.ui.form.on("Bank Clearance", {
frm.trigger("get_payment_entries")
);
frm.change_custom_button_type('Get Payment Entries', null, 'primary');
frm.change_custom_button_type(__('Get Payment Entries'), null, 'primary');
},
update_clearance_date: function(frm) {
@ -53,8 +53,8 @@ frappe.ui.form.on("Bank Clearance", {
frm.refresh_fields();
if (!frm.doc.payment_entries.length) {
frm.change_custom_button_type('Get Payment Entries', null, 'primary');
frm.change_custom_button_type('Update Clearance Date', null, 'default');
frm.change_custom_button_type(__('Get Payment Entries'), null, 'primary');
frm.change_custom_button_type(__('Update Clearance Date'), null, 'default');
}
}
});
@ -72,8 +72,8 @@ frappe.ui.form.on("Bank Clearance", {
frm.trigger("update_clearance_date")
);
frm.change_custom_button_type('Get Payment Entries', null, 'default');
frm.change_custom_button_type('Update Clearance Date', null, 'primary');
frm.change_custom_button_type(__('Get Payment Entries'), null, 'default');
frm.change_custom_button_type(__('Update Clearance Date'), null, 'primary');
}
}
});

View File

@ -81,7 +81,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
frm.add_custom_button(__('Get Unreconciled Entries'), function() {
frm.trigger("make_reconciliation_tool");
});
frm.change_custom_button_type('Get Unreconciled Entries', null, 'primary');
frm.change_custom_button_type(__('Get Unreconciled Entries'), null, 'primary');
},

View File

@ -245,6 +245,7 @@
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
"options": "Phone",
"read_only": 1
},
{
@ -315,10 +316,11 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-08-03 18:55:43.683053",
"modified": "2023-06-03 16:24:01.677026",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Dunning",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@ -365,6 +367,7 @@
],
"sort_field": "modified",
"sort_order": "ASC",
"states": [],
"title_field": "customer_name",
"track_changes": 1
}

View File

@ -952,6 +952,7 @@ class JournalEntry(AccountsController):
blank_row.debit_in_account_currency = abs(diff)
blank_row.debit = abs(diff)
self.set_total_debit_credit()
self.validate_total_debit_and_credit()
@frappe.whitelist()

View File

@ -65,22 +65,22 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
this.frm.add_custom_button(__('Get Unreconciled Entries'), () =>
this.frm.trigger("get_unreconciled_entries")
);
this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'primary');
this.frm.change_custom_button_type(__('Get Unreconciled Entries'), null, 'primary');
}
if (this.frm.doc.invoices.length && this.frm.doc.payments.length) {
this.frm.add_custom_button(__('Allocate'), () =>
this.frm.trigger("allocate")
);
this.frm.change_custom_button_type('Allocate', null, 'primary');
this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'default');
this.frm.change_custom_button_type(__('Allocate'), null, 'primary');
this.frm.change_custom_button_type(__('Get Unreconciled Entries'), null, 'default');
}
if (this.frm.doc.allocation.length) {
this.frm.add_custom_button(__('Reconcile'), () =>
this.frm.trigger("reconcile")
);
this.frm.change_custom_button_type('Reconcile', null, 'primary');
this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'default');
this.frm.change_custom_button_type('Allocate', null, 'default');
this.frm.change_custom_button_type(__('Reconcile'), null, 'primary');
this.frm.change_custom_button_type(__('Get Unreconciled Entries'), null, 'default');
this.frm.change_custom_button_type(__('Allocate'), null, 'default');
}
// check for any running reconciliation jobs

View File

@ -6,7 +6,6 @@ import frappe
from frappe import _, msgprint, qb
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import IfNull
from frappe.utils import flt, get_link_to_form, getdate, nowdate, today
import erpnext
@ -127,12 +126,29 @@ class PaymentReconciliation(Document):
return list(journal_entries)
def get_return_invoices(self):
voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
doc = qb.DocType(voucher_type)
self.return_invoices = (
qb.from_(doc)
.select(
ConstantColumn(voucher_type).as_("voucher_type"),
doc.name.as_("voucher_no"),
doc.return_against,
)
.where(
(doc.docstatus == 1)
& (doc[frappe.scrub(self.party_type)] == self.party)
& (doc.is_return == 1)
)
.run(as_dict=True)
)
def get_dr_or_cr_notes(self):
self.build_qb_filter_conditions(get_return_invoices=True)
ple = qb.DocType("Payment Ledger Entry")
voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
if erpnext.get_party_account_type(self.party_type) == "Receivable":
self.common_filter_conditions.append(ple.account_type == "Receivable")
@ -140,19 +156,10 @@ class PaymentReconciliation(Document):
self.common_filter_conditions.append(ple.account_type == "Payable")
self.common_filter_conditions.append(ple.account == self.receivable_payable_account)
# get return invoices
doc = qb.DocType(voucher_type)
return_invoices = (
qb.from_(doc)
.select(ConstantColumn(voucher_type).as_("voucher_type"), doc.name.as_("voucher_no"))
.where(
(doc.docstatus == 1)
& (doc[frappe.scrub(self.party_type)] == self.party)
& (doc.is_return == 1)
& (IfNull(doc.return_against, "") == "")
)
.run(as_dict=True)
)
self.get_return_invoices()
return_invoices = [
x for x in self.return_invoices if x.return_against == None or x.return_against == ""
]
outstanding_dr_or_cr = []
if return_invoices:
@ -204,6 +211,9 @@ class PaymentReconciliation(Document):
accounting_dimensions=self.accounting_dimension_filter_conditions,
)
cr_dr_notes = [x.voucher_no for x in self.return_invoices]
non_reconciled_invoices = [x for x in non_reconciled_invoices if x.voucher_no not in cr_dr_notes]
if self.invoice_limit:
non_reconciled_invoices = non_reconciled_invoices[: self.invoice_limit]

View File

@ -44,6 +44,7 @@ class PeriodClosingVoucher(AccountsController):
voucher_type="Period Closing Voucher",
voucher_no=self.name,
queue="long",
enqueue_after_commit=True,
)
frappe.msgprint(
_("The GL Entries will be cancelled in the background, it can take a few minutes."), alert=True

View File

@ -442,6 +442,7 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Mobile No",
"options": "Phone",
"read_only": 1
},
{
@ -1554,11 +1555,10 @@
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
"modified": "2022-09-30 03:49:50.455199",
"modified": "2023-06-03 16:23:41.083409",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",
"name_case": "Title Case",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [

View File

@ -158,7 +158,7 @@ def get_customers_based_on_territory_or_customer_group(customer_collection, coll
return frappe.get_list(
"Customer",
fields=["name", "customer_name", "email_id"],
filters=[[fields_dict[customer_collection], "IN", selected]],
filters=[["disabled", "=", 0], [fields_dict[customer_collection], "IN", selected]],
)

View File

@ -443,12 +443,14 @@
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
"options": "Phone",
"read_only": 1
},
{
"fieldname": "contact_email",
"fieldtype": "Small Text",
"label": "Contact Email",
"options": "Email",
"print_hide": 1,
"read_only": 1
},
@ -1364,12 +1366,12 @@
"depends_on": "eval:doc.update_stock && doc.is_internal_supplier",
"fieldname": "set_from_warehouse",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Set From Warehouse",
"no_copy": 1,
"options": "Warehouse",
"print_hide": 1,
"print_width": "50px",
"ignore_user_permissions": 1,
"width": "50px"
},
{
@ -1573,7 +1575,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2023-04-29 12:57:50.832598",
"modified": "2023-06-03 16:21:54.637245",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@ -520,6 +520,7 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Mobile No",
"options": "Phone",
"read_only": 1
},
{
@ -2154,7 +2155,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2023-04-28 14:15:59.901154",
"modified": "2023-06-03 16:22:16.219333",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@ -3,8 +3,10 @@
import frappe
from frappe import _
from frappe import _, qb
from frappe.model.document import Document
from frappe.query_builder import Criterion
from frappe.query_builder.functions import Abs, Sum
from frappe.utils import cint, getdate
@ -346,26 +348,33 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
def get_advance_vouchers(
parties, company=None, from_date=None, to_date=None, party_type="Supplier"
):
# for advance vouchers, debit and credit is reversed
dr_or_cr = "debit" if party_type == "Supplier" else "credit"
"""
Use Payment Ledger to fetch unallocated Advance Payments
"""
filters = {
dr_or_cr: [">", 0],
"is_opening": "No",
"is_cancelled": 0,
"party_type": party_type,
"party": ["in", parties],
}
ple = qb.DocType("Payment Ledger Entry")
if party_type == "Customer":
filters.update({"against_voucher": ["is", "not set"]})
conditions = []
conditions.append(ple.amount.lt(0))
conditions.append(ple.delinked == 0)
conditions.append(ple.party_type == party_type)
conditions.append(ple.party.isin(parties))
conditions.append(ple.voucher_no == ple.against_voucher_no)
if company:
filters["company"] = company
if from_date and to_date:
filters["posting_date"] = ["between", (from_date, to_date)]
conditions.append(ple.company == company)
return frappe.get_all("GL Entry", filters=filters, distinct=1, pluck="voucher_no") or [""]
if from_date and to_date:
conditions.append(ple.posting_date[from_date:to_date])
advances = (
qb.from_(ple).select(ple.voucher_no).distinct().where(Criterion.all(conditions)).run(as_list=1)
)
if advances:
advances = [x[0] for x in advances]
return advances
def get_taxes_deducted_on_advances_allocated(inv, tax_details):
@ -499,6 +508,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
tcs_amount = 0
ple = qb.DocType("Payment Ledger Entry")
# sum of debit entries made from sales invoices
invoiced_amt = (
@ -516,18 +526,20 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
)
# sum of credit entries made from PE / JV with unset 'against voucher'
conditions = []
conditions.append(ple.amount.lt(0))
conditions.append(ple.delinked == 0)
conditions.append(ple.party.isin(parties))
conditions.append(ple.voucher_no == ple.against_voucher_no)
conditions.append(ple.company == inv.company)
advances = (
qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run(as_list=1)
)
advance_amt = (
frappe.db.get_value(
"GL Entry",
{
"is_cancelled": 0,
"party": ["in", parties],
"company": inv.company,
"voucher_no": ["in", adv_vouchers],
},
"sum(credit)",
)
or 0.0
qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run()[0][0] or 0.0
)
# sum of credit entries made from sales invoice

View File

@ -152,6 +152,60 @@ class TestTaxWithholdingCategory(unittest.TestCase):
for d in reversed(invoices):
d.cancel()
def test_tcs_on_unallocated_advance_payments(self):
frappe.db.set_value(
"Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS"
)
vouchers = []
# create advance payment
pe = create_payment_entry(
payment_type="Receive", party_type="Customer", party="Test TCS Customer", paid_amount=20000
)
pe.paid_from = "Debtors - _TC"
pe.paid_to = "Cash - _TC"
pe.submit()
vouchers.append(pe)
# create invoice
si1 = create_sales_invoice(customer="Test TCS Customer", rate=5000)
si1.submit()
vouchers.append(si1)
# reconcile
pr = frappe.get_doc("Payment Reconciliation")
pr.company = "_Test Company"
pr.party_type = "Customer"
pr.party = "Test TCS Customer"
pr.receivable_payable_account = "Debtors - _TC"
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
# make another invoice
# sum of unallocated amount from payment entry and this sales invoice will breach cumulative threashold
# TDS should be calculated
si2 = create_sales_invoice(customer="Test TCS Customer", rate=15000)
si2.submit()
vouchers.append(si2)
si3 = create_sales_invoice(customer="Test TCS Customer", rate=10000)
si3.submit()
vouchers.append(si3)
# assert tax collection on total invoice amount created until now
tcs_charged = sum([d.base_tax_amount for d in si2.taxes if d.account_head == "TCS - _TC"])
tcs_charged += sum([d.base_tax_amount for d in si3.taxes if d.account_head == "TCS - _TC"])
self.assertEqual(tcs_charged, 1500)
# cancel invoice and payments to avoid clashing
for d in reversed(vouchers):
d.reload()
d.cancel()
def test_tds_calculation_on_net_total(self):
frappe.db.set_value(
"Supplier", "Test TDS Supplier4", "tax_withholding_category", "Cumulative Threshold TDS"

View File

@ -2,6 +2,8 @@
# License: GNU General Public License v3. See license.txt
from typing import Optional
import frappe
from frappe import _, msgprint, scrub
from frappe.contacts.doctype.address.address import (
@ -647,12 +649,12 @@ def set_taxes(
else:
args.update(get_party_details(party, party_type))
if party_type in ("Customer", "Lead"):
if party_type in ("Customer", "Lead", "Prospect"):
args.update({"tax_type": "Sales"})
if party_type == "Lead":
if party_type in ["Lead", "Prospect"]:
args["customer"] = None
del args["lead"]
del args[frappe.scrub(party_type)]
else:
args.update({"tax_type": "Purchase"})
@ -850,7 +852,7 @@ def get_dashboard_info(party_type, party, loyalty_program=None):
return company_wise_info
def get_party_shipping_address(doctype, name):
def get_party_shipping_address(doctype: str, name: str) -> Optional[str]:
"""
Returns an Address name (best guess) for the given doctype and name for which `address_type == 'Shipping'` is true.
and/or `is_shipping_address = 1`.
@ -861,22 +863,23 @@ def get_party_shipping_address(doctype, name):
:param name: Party name
:return: String
"""
out = frappe.db.sql(
"SELECT dl.parent "
"from `tabDynamic Link` dl join `tabAddress` ta on dl.parent=ta.name "
"where "
"dl.link_doctype=%s "
"and dl.link_name=%s "
"and dl.parenttype='Address' "
"and ifnull(ta.disabled, 0) = 0 and"
"(ta.address_type='Shipping' or ta.is_shipping_address=1) "
"order by ta.is_shipping_address desc, ta.address_type desc limit 1",
(doctype, name),
shipping_addresses = frappe.get_all(
"Address",
filters=[
["Dynamic Link", "link_doctype", "=", doctype],
["Dynamic Link", "link_name", "=", name],
["disabled", "=", 0],
],
or_filters=[
["is_shipping_address", "=", 1],
["address_type", "=", "Shipping"],
],
pluck="name",
limit=1,
order_by="is_shipping_address DESC",
)
if out:
return out[0][0]
else:
return ""
return shipping_addresses[0] if shipping_addresses else None
def get_partywise_advanced_payment_amount(
@ -910,31 +913,32 @@ def get_partywise_advanced_payment_amount(
return frappe._dict(data)
def get_default_contact(doctype, name):
def get_default_contact(doctype: str, name: str) -> Optional[str]:
"""
Returns default contact for the given doctype and name.
Can be ordered by `contact_type` to either is_primary_contact or is_billing_contact.
Returns contact name only if there is a primary contact for given doctype and name.
Else returns None
:param doctype: Party Doctype
:param name: Party name
:return: String
"""
out = frappe.db.sql(
"""
SELECT dl.parent, c.is_primary_contact, c.is_billing_contact
FROM `tabDynamic Link` dl
INNER JOIN `tabContact` c ON c.name = dl.parent
WHERE
dl.link_doctype=%s AND
dl.link_name=%s AND
dl.parenttype = 'Contact'
ORDER BY is_primary_contact DESC, is_billing_contact DESC
""",
(doctype, name),
contacts = frappe.get_all(
"Contact",
filters=[
["Dynamic Link", "link_doctype", "=", doctype],
["Dynamic Link", "link_name", "=", name],
],
or_filters=[
["is_primary_contact", "=", 1],
["is_billing_contact", "=", 1],
],
pluck="name",
limit=1,
order_by="is_primary_contact DESC, is_billing_contact DESC",
)
if out:
try:
return out[0][0]
except Exception:
return None
else:
return None
return contacts[0] if contacts else None
def add_party_account(party_type, party, company, account):

View File

@ -181,6 +181,16 @@ class ReceivablePayableReport(object):
return
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
# If payment is made against credit note
# and credit note is made against a Sales Invoice
# then consider the payment against original sales invoice.
if ple.against_voucher_type in ("Sales Invoice", "Purchase Invoice"):
if ple.against_voucher_no in self.return_entries:
return_against = self.return_entries.get(ple.against_voucher_no)
if return_against:
key = (ple.against_voucher_type, return_against, ple.party)
row = self.voucher_balance.get(key)
if not row:
@ -610,7 +620,7 @@ class ReceivablePayableReport(object):
def get_return_entries(self):
doctype = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
filters = {"is_return": 1, "docstatus": 1}
filters = {"is_return": 1, "docstatus": 1, "company": self.filters.company}
party_field = scrub(self.filters.party_type)
if self.filters.get(party_field):
filters.update({party_field: self.filters.get(party_field)})

View File

@ -210,6 +210,67 @@ class TestAccountsReceivable(FrappeTestCase):
],
)
def test_payment_against_credit_note(self):
"""
Payment against credit/debit note should be considered against the parent invoice
"""
company = "_Test Company 2"
customer = "_Test Customer 2"
si1 = make_sales_invoice()
pe = get_payment_entry("Sales Invoice", si1.name, bank_account="Cash - _TC2")
pe.paid_from = "Debtors - _TC2"
pe.insert()
pe.submit()
cr_note = make_credit_note(si1.name)
si2 = make_sales_invoice()
# manually link cr_note with si2 using journal entry
je = frappe.new_doc("Journal Entry")
je.company = company
je.voucher_type = "Credit Note"
je.posting_date = today()
debit_account = "Debtors - _TC2"
debit_entry = {
"account": debit_account,
"party_type": "Customer",
"party": customer,
"debit": 100,
"debit_in_account_currency": 100,
"reference_type": cr_note.doctype,
"reference_name": cr_note.name,
"cost_center": "Main - _TC2",
}
credit_entry = {
"account": debit_account,
"party_type": "Customer",
"party": customer,
"credit": 100,
"credit_in_account_currency": 100,
"reference_type": si2.doctype,
"reference_name": si2.name,
"cost_center": "Main - _TC2",
}
je.append("accounts", debit_entry)
je.append("accounts", credit_entry)
je = je.save().submit()
filters = {
"company": company,
"report_date": today(),
"range1": 30,
"range2": 60,
"range3": 90,
"range4": 120,
}
report = execute(filters)
self.assertEqual(report[1], [])
def make_sales_invoice(no_payment_schedule=False, do_not_submit=False):
frappe.set_user("Administrator")
@ -256,7 +317,7 @@ def make_payment(docname):
def make_credit_note(docname):
create_sales_invoice(
credit_note = create_sales_invoice(
company="_Test Company 2",
customer="_Test Customer 2",
currency="EUR",
@ -269,3 +330,5 @@ def make_credit_note(docname):
is_return=1,
return_against=docname,
)
return credit_note

View File

@ -399,8 +399,9 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
`tabSales Invoice`.unrealized_profit_loss_account,
`tabSales Invoice`.is_internal_customer,
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
`tabSales Invoice`.customer, `tabSales Invoice`.remarks,
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
`tabSales Invoice Item`.project,
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`,
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,

View File

@ -812,14 +812,14 @@ class TestDepreciationMethods(AssetSetup):
number_of_depreciations_booked=1,
opening_accumulated_depreciation=50000,
expected_value_after_useful_life=10000,
depreciation_start_date="2030-12-31",
depreciation_start_date="2031-12-31",
total_number_of_depreciations=3,
frequency_of_depreciation=12,
)
self.assertEqual(asset.status, "Draft")
expected_schedules = [["2030-12-31", 33333.50, 83333.50], ["2031-12-31", 6666.50, 90000.0]]
expected_schedules = [["2031-12-31", 33333.50, 83333.50], ["2032-12-31", 6666.50, 90000.0]]
schedules = [
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]

View File

@ -10,6 +10,7 @@ from frappe.utils import (
cint,
date_diff,
flt,
get_first_day,
get_last_day,
getdate,
is_last_day_of_the_month,
@ -271,8 +272,14 @@ class AssetDepreciationSchedule(Document):
break
# For first row
if n == 0 and has_pro_rata and not self.opening_accumulated_depreciation:
from_date = add_days(asset_doc.available_for_use_date, -1)
if (
n == 0
and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
and not self.opening_accumulated_depreciation
):
from_date = add_days(
asset_doc.available_for_use_date, -1
) # needed to calc depr amount for available_for_use_date too
depreciation_amount, days, months = _get_pro_rata_amt(
row,
depreciation_amount,
@ -281,10 +288,18 @@ class AssetDepreciationSchedule(Document):
has_wdv_or_dd_non_yearly_pro_rata,
)
elif n == 0 and has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation:
from_date = add_months(
getdate(asset_doc.available_for_use_date),
(self.number_of_depreciations_booked * row.frequency_of_depreciation),
)
if not is_first_day_of_the_month(getdate(asset_doc.available_for_use_date)):
from_date = get_last_day(
add_months(
getdate(asset_doc.available_for_use_date),
((self.number_of_depreciations_booked - 1) * row.frequency_of_depreciation),
)
)
else:
from_date = add_months(
getdate(add_days(asset_doc.available_for_use_date, -1)),
(self.number_of_depreciations_booked * row.frequency_of_depreciation),
)
depreciation_amount, days, months = _get_pro_rata_amt(
row,
depreciation_amount,
@ -702,3 +717,9 @@ def get_asset_depr_schedule_name(asset_name, status, finance_book=None):
["status", "=", status],
],
)
def is_first_day_of_the_month(date):
first_day_of_the_month = get_first_day(date)
return getdate(first_day_of_the_month) == getdate(date)

View File

@ -322,6 +322,7 @@
"fieldtype": "Small Text",
"hidden": 1,
"label": "Customer Mobile No",
"options": "Phone",
"print_hide": 1
},
{
@ -368,6 +369,7 @@
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Contact Mobile No",
"options": "Phone",
"read_only": 1
},
{
@ -1271,7 +1273,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2023-05-24 11:16:41.195340",
"modified": "2023-06-03 16:19:45.710444",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

View File

@ -230,6 +230,7 @@
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
"options": "Phone",
"read_only": 1
},
{
@ -844,7 +845,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-04-14 16:43:41.714832",
"modified": "2023-06-03 16:20:15.880114",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",

View File

@ -43,7 +43,6 @@ class SellingController(StockController):
self.set_serial_and_batch_bundle(table_field)
def set_missing_values(self, for_validate=False):
super(SellingController, self).set_missing_values(for_validate)
# set contact and address details for customer, if they are not mentioned
@ -62,7 +61,7 @@ class SellingController(StockController):
elif self.doctype == "Quotation" and self.party_name:
if self.quotation_to == "Customer":
customer = self.party_name
else:
elif self.quotation_to == "Lead":
lead = self.party_name
if customer:

View File

@ -3,7 +3,10 @@
import frappe
from frappe import _
from frappe.contacts.address_and_contact import load_address_and_contact
from frappe.contacts.address_and_contact import (
delete_contact_and_address,
load_address_and_contact,
)
from frappe.email.inbox import link_communication_to_document
from frappe.model.mapper import get_mapped_doc
from frappe.utils import comma_and, get_link_to_form, has_gravatar, validate_email_address
@ -40,9 +43,8 @@ class Lead(SellingController, CRMNote):
self.update_prospect()
def on_trash(self):
frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""", self.name)
self.unlink_dynamic_links()
frappe.db.set_value("Issue", {"lead": self.name}, "lead", None)
delete_contact_and_address(self.doctype, self.name)
self.remove_link_from_prospect()
def set_full_name(self):
@ -119,27 +121,6 @@ class Lead(SellingController, CRMNote):
)
lead_row.db_update()
def unlink_dynamic_links(self):
links = frappe.get_all(
"Dynamic Link",
filters={"link_doctype": self.doctype, "link_name": self.name},
fields=["parent", "parenttype"],
)
for link in links:
linked_doc = frappe.get_doc(link["parenttype"], link["parent"])
if len(linked_doc.get("links")) == 1:
linked_doc.delete(ignore_permissions=True)
else:
to_remove = None
for d in linked_doc.get("links"):
if d.link_doctype == self.doctype and d.link_name == self.name:
to_remove = d
if to_remove:
linked_doc.remove(to_remove)
linked_doc.save(ignore_permissions=True)
def remove_link_from_prospect(self):
prospects = self.get_linked_prospects()

View File

@ -2,7 +2,10 @@
# For license information, please see license.txt
import frappe
from frappe.contacts.address_and_contact import load_address_and_contact
from frappe.contacts.address_and_contact import (
delete_contact_and_address,
load_address_and_contact,
)
from frappe.model.mapper import get_mapped_doc
from erpnext.crm.utils import CRMNote, copy_comments, link_communications, link_open_events
@ -16,7 +19,7 @@ class Prospect(CRMNote):
self.link_with_lead_contact_and_address()
def on_trash(self):
self.unlink_dynamic_links()
delete_contact_and_address(self.doctype, self.name)
def after_insert(self):
carry_forward_communication_and_comments = frappe.db.get_single_value(
@ -54,27 +57,6 @@ class Prospect(CRMNote):
linked_doc.append("links", {"link_doctype": self.doctype, "link_name": self.name})
linked_doc.save(ignore_permissions=True)
def unlink_dynamic_links(self):
links = frappe.get_all(
"Dynamic Link",
filters={"link_doctype": self.doctype, "link_name": self.name},
fields=["parent", "parenttype"],
)
for link in links:
linked_doc = frappe.get_doc(link["parenttype"], link["parent"])
if len(linked_doc.get("links")) == 1:
linked_doc.delete(ignore_permissions=True)
else:
to_remove = None
for d in linked_doc.get("links"):
if d.link_doctype == self.doctype and d.link_name == self.name:
to_remove = d
if to_remove:
linked_doc.remove(to_remove)
linked_doc.save(ignore_permissions=True)
@frappe.whitelist()
def make_customer(source_name, target_doc=None):

View File

@ -78,9 +78,10 @@ erpnext.ProductList = class {
let title_html = `<div style="display: flex; margin-left: -15px;">`;
title_html += `
<div class="col-8" style="margin-right: -15px;">
<a class="" href="/${ item.route || '#' }"
style="color: var(--gray-800); font-weight: 500;">
<a href="/${ item.route || '#' }">
<div class="product-title">
${ title }
</div>
</a>
</div>
`;
@ -201,4 +202,4 @@ erpnext.ProductList = class {
}
}
};
};

View File

@ -160,4 +160,3 @@ class TestLoanDisbursement(unittest.TestCase):
interest = per_day_interest * 15
self.assertEqual(amounts["pending_principal_amount"], 1500000)
self.assertEqual(amounts["interest_amount"], flt(interest + previous_interest, 2))

View File

@ -22,7 +22,7 @@ class LoanInterestAccrual(AccountsController):
frappe.throw(_("Interest Amount or Principal Amount is mandatory"))
if not self.last_accrual_date:
self.last_accrual_date = get_last_accrual_date(self.loan)
self.last_accrual_date = get_last_accrual_date(self.loan, self.posting_date)
def on_submit(self):
self.make_gl_entries()
@ -274,14 +274,14 @@ def make_loan_interest_accrual_entry(args):
def get_no_of_days_for_interest_accural(loan, posting_date):
last_interest_accrual_date = get_last_accrual_date(loan.name)
last_interest_accrual_date = get_last_accrual_date(loan.name, posting_date)
no_of_days = date_diff(posting_date or nowdate(), last_interest_accrual_date) + 1
return no_of_days
def get_last_accrual_date(loan):
def get_last_accrual_date(loan, posting_date):
last_posting_date = frappe.db.sql(
""" SELECT MAX(posting_date) from `tabLoan Interest Accrual`
WHERE loan = %s and docstatus = 1""",
@ -289,12 +289,30 @@ def get_last_accrual_date(loan):
)
if last_posting_date[0][0]:
last_interest_accrual_date = last_posting_date[0][0]
# interest for last interest accrual date is already booked, so add 1 day
return add_days(last_posting_date[0][0], 1)
last_disbursement_date = get_last_disbursement_date(loan, posting_date)
if last_disbursement_date and getdate(last_disbursement_date) > getdate(
last_interest_accrual_date
):
last_interest_accrual_date = last_disbursement_date
return add_days(last_interest_accrual_date, 1)
else:
return frappe.db.get_value("Loan", loan, "disbursement_date")
def get_last_disbursement_date(loan, posting_date):
last_disbursement_date = frappe.db.get_value(
"Loan Disbursement",
{"docstatus": 1, "against_loan": loan, "posting_date": ("<", posting_date)},
"MAX(posting_date)",
)
return last_disbursement_date
def days_in_year(year):
days = 365

View File

@ -101,7 +101,7 @@ class LoanRepayment(AccountsController):
if flt(self.total_interest_paid, precision) > flt(self.interest_payable, precision):
if not self.is_term_loan:
# get last loan interest accrual date
last_accrual_date = get_last_accrual_date(self.against_loan)
last_accrual_date = get_last_accrual_date(self.against_loan, self.posting_date)
# get posting date upto which interest has to be accrued
per_day_interest = get_per_day_interest(
@ -725,7 +725,7 @@ def get_amounts(amounts, against_loan, posting_date):
if due_date:
pending_days = date_diff(posting_date, due_date) + 1
else:
last_accrual_date = get_last_accrual_date(against_loan_doc.name)
last_accrual_date = get_last_accrual_date(against_loan_doc.name, posting_date)
pending_days = date_diff(posting_date, last_accrual_date) + 1
if pending_days > 0:

View File

@ -152,6 +152,7 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Mobile No",
"options": "Phone",
"read_only": 1
},
{
@ -160,6 +161,7 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Contact Email",
"options": "Email",
"print_hide": 1,
"read_only": 1
},
@ -236,10 +238,11 @@
"link_fieldname": "maintenance_schedule"
}
],
"modified": "2021-05-27 16:05:10.746465",
"modified": "2023-06-03 16:15:43.958072",
"modified_by": "Administrator",
"module": "Maintenance",
"name": "Maintenance Schedule",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@ -260,5 +263,6 @@
"search_fields": "status,customer,customer_name",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"timeline_field": "customer"
}

View File

@ -101,6 +101,7 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Mobile No",
"options": "Phone",
"read_only": 1
},
{
@ -108,6 +109,7 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Contact Email",
"options": "Email",
"read_only": 1
},
{
@ -293,7 +295,7 @@
"idx": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-12-17 03:10:27.608112",
"modified": "2023-06-03 16:19:07.902723",
"modified_by": "Administrator",
"module": "Maintenance",
"name": "Maintenance Visit",
@ -319,6 +321,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"timeline_field": "customer",
"title_field": "customer_name"
}

View File

@ -88,12 +88,14 @@ class BOMUpdateLog(Document):
boms=boms,
timeout=40000,
now=frappe.flags.in_test,
enqueue_after_commit=True,
)
else:
frappe.enqueue(
method="erpnext.manufacturing.doctype.bom_update_log.bom_update_log.process_boms_cost_level_wise",
update_doc=self,
now=frappe.flags.in_test,
enqueue_after_commit=True,
)

View File

@ -304,6 +304,7 @@ def set_tasks_as_overdue():
@frappe.whitelist()
def make_timesheet(source_name, target_doc=None, ignore_permissions=False):
def set_missing_values(source, target):
target.parent_project = source.project
target.append(
"time_logs",
{

View File

@ -40,8 +40,8 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager {
name: __("Date"),
editable: false,
width: 100,
format: frappe.form.formatters.Date,
},
{
name: __("Party Type"),
editable: false,
@ -117,17 +117,13 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager {
return [
row["date"],
row["party_type"],
row["party"],
frappe.form.formatters.Link(row["party"], {options: row["party_type"]}),
row["description"],
row["deposit"],
row["withdrawal"],
row["unallocated_amount"],
row["reference_number"],
`
<Button class="btn btn-primary btn-xs center" data-name = ${row["name"]} >
${__("Actions")}
</a>
`,
`<button class="btn btn-primary btn-xs center" data-name="${row["name"]}">${__("Actions")}</button>`
];
}

View File

@ -76,30 +76,17 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
callback: (result) => {
const data = result.message;
if (data && data.length > 0) {
const proposals_wrapper = this.dialog.fields_dict.payment_proposals.$wrapper;
proposals_wrapper.show();
this.dialog.fields_dict.no_matching_vouchers.$wrapper.hide();
this.data = [];
data.forEach((row) => {
const reference_date = row[5] ? row[5] : row[8];
this.data.push([
row[1],
row[2],
reference_date,
format_currency(row[3], row[9]),
row[4],
row[6],
]);
});
this.data = data.map((row) => this.format_row(row));
this.get_dt_columns();
this.get_datatable(proposals_wrapper);
} else {
const proposals_wrapper = this.dialog.fields_dict.payment_proposals.$wrapper;
proposals_wrapper.hide();
this.dialog.fields_dict.no_matching_vouchers.$wrapper.show();
}
this.dialog.show();
},
@ -122,6 +109,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
name: __("Reference Date"),
editable: false,
width: 120,
format: frappe.form.formatters.Date,
},
{
name: __("Remaining"),
@ -141,6 +129,17 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
];
}
format_row(row) {
return [
row[1], // Document Type
frappe.form.formatters.Link(row[2], {options: row[1]}), // Document Name
row[5] || row[8], // Reference Date
format_currency(row[3], row[9]), // Remaining
row[4], // Reference Number
row[6], // Party
];
}
get_datatable(proposals_wrapper) {
if (!this.datatable) {
const datatable_options = {

View File

@ -805,11 +805,13 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
);
}
this.frm.doc.payments.find(pay => {
if (pay.default) {
pay.amount = total_amount_to_pay;
}
});
if(!this.frm.doc.is_return){
this.frm.doc.payments.find(payment => {
if (payment.default) {
payment.amount = total_amount_to_pay;
}
});
}
this.frm.refresh_fields();
}

View File

@ -16,8 +16,8 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) {
|| (frm.doc.party_name && in_list(['Quotation', 'Opportunity'], frm.doc.doctype))) {
let party_type = "Customer";
if (frm.doc.quotation_to && frm.doc.quotation_to === "Lead") {
party_type = "Lead";
if (frm.doc.quotation_to && in_list(["Lead", "Prospect"], frm.doc.quotation_to)) {
party_type = frm.doc.quotation_to;
}
args = {

View File

@ -454,12 +454,12 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False,
customer_outstanding += flt(extra_amount)
if credit_limit > 0 and flt(customer_outstanding) > credit_limit:
msgprint(
_("Credit limit has been crossed for customer {0} ({1}/{2})").format(
customer, customer_outstanding, credit_limit
)
message = _("Credit limit has been crossed for customer {0} ({1}/{2})").format(
customer, customer_outstanding, credit_limit
)
message += "<br><br>"
# If not authorized person raise exception
credit_controller_role = frappe.db.get_single_value("Accounts Settings", "credit_controller")
if not credit_controller_role or credit_controller_role not in frappe.get_roles():
@ -480,7 +480,7 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False,
"<li>".join(credit_controller_users_formatted)
)
message = _(
message += _(
"Please contact any of the following users to extend the credit limits for {0}: {1}"
).format(customer, user_list)
@ -488,7 +488,7 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False,
# prompt them to send out an email to the controller users
frappe.msgprint(
message,
title="Notify",
title=_("Credit Limit Crossed"),
raise_exception=1,
primary_action={
"label": "Send Email",
@ -519,7 +519,6 @@ def get_customer_outstanding(
customer, company, ignore_outstanding_sales_order=False, cost_center=None
):
# Outstanding based on GL Entries
cond = ""
if cost_center:
lft, rgt = frappe.get_cached_value("Cost Center", cost_center, ["lft", "rgt"])

View File

@ -13,7 +13,7 @@ frappe.ui.form.on('Quotation', {
frm.set_query("quotation_to", function() {
return{
"filters": {
"name": ["in", ["Customer", "Lead"]],
"name": ["in", ["Customer", "Lead", "Prospect"]],
}
}
});
@ -160,19 +160,16 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext.
}
set_dynamic_field_label(){
if (this.frm.doc.quotation_to == "Customer")
{
if (this.frm.doc.quotation_to == "Customer") {
this.frm.set_df_property("party_name", "label", "Customer");
this.frm.fields_dict.party_name.get_query = null;
}
if (this.frm.doc.quotation_to == "Lead")
{
} else if (this.frm.doc.quotation_to == "Lead") {
this.frm.set_df_property("party_name", "label", "Lead");
this.frm.fields_dict.party_name.get_query = function() {
return{ query: "erpnext.controllers.queries.lead_query" }
}
} else if (this.frm.doc.quotation_to == "Prospect") {
this.frm.set_df_property("party_name", "label", "Prospect");
}
}

View File

@ -291,6 +291,7 @@
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
"options": "Phone",
"read_only": 1
},
{
@ -1072,7 +1073,7 @@
"idx": 82,
"is_submittable": 1,
"links": [],
"modified": "2023-04-14 16:50:44.550098",
"modified": "2023-06-03 16:21:04.980033",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation",

View File

@ -398,6 +398,7 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Mobile No",
"options": "Phone",
"read_only": 1
},
{
@ -1475,6 +1476,7 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Phone",
"options": "Phone",
"read_only": 1
},
{
@ -1643,7 +1645,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2023-04-22 09:55:37.008190",
"modified": "2023-06-03 16:16:23.411247",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",

View File

@ -230,6 +230,7 @@ class SalesOrder(SellingController):
frappe.throw(_("Quotation {0} is cancelled").format(quotation))
doc.set_status(update=True)
doc.update_opportunity("Converted" if flag == "submit" else "Quotation")
def validate_drop_ship(self):
for d in self.get("items"):

View File

@ -1772,7 +1772,14 @@ class TestSalesOrder(FrappeTestCase):
self.assertEqual(pe.references[1].reference_name, so.name)
self.assertEqual(pe.references[1].allocated_amount, 300)
@change_settings("Stock Settings", {"enable_stock_reservation": 1})
@change_settings(
"Stock Settings",
{
"enable_stock_reservation": 1,
"auto_create_serial_and_batch_bundle_for_outward": 1,
"pick_serial_and_batch_based_on": "FIFO",
},
)
def test_stock_reservation_against_sales_order(self) -> None:
from random import randint, uniform

View File

@ -25,6 +25,7 @@ def after_install():
create_default_success_action()
create_default_energy_point_rules()
create_incoterms()
create_default_role_profiles()
add_company_to_session_defaults()
add_standard_navbar_items()
add_app_name()
@ -202,3 +203,42 @@ def setup_log_settings():
def hide_workspaces():
for ws in ["Integration", "Settings"]:
frappe.db.set_value("Workspace", ws, "public", 0)
def create_default_role_profiles():
for role_profile_name, roles in DEFAULT_ROLE_PROFILES.items():
role_profile = frappe.new_doc("Role Profile")
role_profile.role_profile = role_profile_name
for role in roles:
role_profile.append("roles", {"role": role})
role_profile.insert(ignore_permissions=True)
DEFAULT_ROLE_PROFILES = {
"Inventory": [
"Stock User",
"Stock Manager",
"Item Manager",
],
"Manufacturing": [
"Stock User",
"Manufacturing User",
"Manufacturing Manager",
],
"Accounts": [
"Accounts User",
"Accounts Manager",
],
"Sales": [
"Sales User",
"Stock User",
"Sales Manager",
],
"Purchase": [
"Item Manager",
"Stock User",
"Purchase User",
"Purchase Manager",
],
}

View File

@ -51,7 +51,7 @@ class ClosingStockBalance(Document):
for fieldname in ["warehouse", "item_code", "item_group", "warehouse_type"]:
if self.get(fieldname):
query = query.where(table.get(fieldname) == self.get(fieldname))
query = query.where(table[fieldname] == self.get(fieldname))
query = query.run(as_dict=True)

View File

@ -374,6 +374,7 @@
"fieldtype": "Small Text",
"hidden": 1,
"label": "Mobile No",
"options": "Phone",
"read_only": 1
},
{
@ -1398,7 +1399,7 @@
"idx": 146,
"is_submittable": 1,
"links": [],
"modified": "2023-04-21 11:15:23.931084",
"modified": "2023-06-03 16:13:25.011487",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
@ -1468,4 +1469,4 @@
"title_field": "title",
"track_changes": 1,
"track_seen": 1
}
}

View File

@ -772,12 +772,6 @@ $.extend(erpnext.item, {
if (modal) {
$(modal).removeClass("modal-dialog-scrollable");
}
})
.on("awesomplete-close", () => {
let modal = field.$input.parents('.modal-dialog')[0];
if (modal) {
$(modal).addClass("modal-dialog-scrollable");
}
});
});
},

View File

@ -714,6 +714,7 @@ class Item(Document):
template=self,
now=frappe.flags.in_test,
timeout=600,
enqueue_after_commit=True,
)
def validate_has_variants(self):

View File

@ -326,6 +326,7 @@
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
"options": "Phone",
"read_only": 1
},
{
@ -1239,7 +1240,7 @@
"idx": 261,
"is_submittable": 1,
"links": [],
"modified": "2023-05-07 20:18:25.458185",
"modified": "2023-06-03 16:23:20.781368",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",

View File

@ -127,6 +127,14 @@ frappe.ui.form.on('Serial and Batch Bundle', {
},
toggle_fields(frm) {
if (frm.doc.has_serial_no) {
frm.doc.entries.forEach(row => {
if (Math.abs(row.qty) !== 1) {
frappe.model.set_value(row.doctype, row.name, "qty", 1);
}
})
}
frm.fields_dict.entries.grid.update_docfield_property(
'serial_no', 'read_only', !frm.doc.has_serial_no
);
@ -134,6 +142,10 @@ frappe.ui.form.on('Serial and Batch Bundle', {
frm.fields_dict.entries.grid.update_docfield_property(
'batch_no', 'read_only', !frm.doc.has_batch_no
);
frm.fields_dict.entries.grid.update_docfield_property(
'qty', 'read_only', frm.doc.has_serial_no
);
},
set_queries(frm) {
@ -198,9 +210,9 @@ frappe.ui.form.on('Serial and Batch Bundle', {
frappe.ui.form.on("Serial and Batch Entry", {
ledgers_add(frm, cdt, cdn) {
entries_add(frm, cdt, cdn) {
if (frm.doc.warehouse) {
locals[cdt][cdn].warehouse = frm.doc.warehouse;
frappe.model.set_value(cdt, cdn, 'warehouse', frm.doc.warehouse);
}
},
})

View File

@ -133,7 +133,7 @@ class SerialandBatchBundle(Document):
def calculate_total_qty(self, save=True):
self.total_qty = 0.0
for d in self.entries:
d.qty = abs(d.qty) if d.qty else 0
d.qty = 1 if self.has_serial_no and abs(d.qty) > 1 else abs(d.qty) if d.qty else 0
d.stock_value_difference = abs(d.stock_value_difference) if d.stock_value_difference else 0
if self.type_of_transaction == "Outward":
d.qty *= -1

View File

@ -0,0 +1,11 @@
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.listview_settings["Serial and Batch Bundle"] = {
add_fields: ["is_cancelled"],
get_indicator: function (doc) {
if (doc.is_cancelled) {
return [__("Cancelled"), "red", "is_cancelled,=,1"];
}
},
};

View File

@ -94,6 +94,7 @@ class StockSettings(Document):
frappe.enqueue(
"erpnext.stock.doctype.stock_settings.stock_settings.clean_all_descriptions",
now=frappe.flags.in_test,
enqueue_after_commit=True,
)
def validate_pending_reposts(self):

View File

@ -944,7 +944,7 @@ class update_entries_after(object):
for item in sr.items:
# Skip for Serial and Batch Items
if item.serial_no or item.batch_no:
if item.name != sle.voucher_detail_no or item.serial_no or item.batch_no:
continue
previous_sle = get_previous_sle(

View File

@ -205,6 +205,7 @@
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
"options": "Phone",
"read_only": 1
},
{
@ -629,7 +630,7 @@
"in_create": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-11-16 14:18:57.001239",
"modified": "2023-06-03 16:18:39.088518",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt",

View File

@ -1,9 +1,11 @@
{
"actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-01-10 16:34:30",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
"naming_series",
"status",
@ -249,6 +251,7 @@
"fieldname": "contact_mobile",
"fieldtype": "Data",
"label": "Mobile No",
"options": "Phone",
"read_only": 1
},
{
@ -362,10 +365,12 @@
],
"icon": "fa fa-bug",
"idx": 1,
"modified": "2021-11-09 17:26:09.703215",
"links": [],
"modified": "2023-06-03 16:17:07.694449",
"modified_by": "Administrator",
"module": "Support",
"name": "Warranty Claim",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@ -384,6 +389,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"timeline_field": "customer",
"title_field": "customer_name"
}
}