mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-04 14:08:29 +00:00
Merge pull request #44341 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -515,6 +515,55 @@ class TestJournalEntry(unittest.TestCase):
|
||||
self.assertEqual(row.debit_in_account_currency, 100)
|
||||
self.assertEqual(row.credit_in_account_currency, 100)
|
||||
|
||||
def test_toggle_debit_credit_if_negative(self):
|
||||
from erpnext.accounts.general_ledger import process_gl_map
|
||||
|
||||
# Create JV with defaut cost center - _Test Cost Center
|
||||
frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0)
|
||||
|
||||
jv = frappe.new_doc("Journal Entry")
|
||||
jv.posting_date = nowdate()
|
||||
jv.company = "_Test Company"
|
||||
jv.user_remark = "test"
|
||||
jv.extend(
|
||||
"accounts",
|
||||
[
|
||||
{
|
||||
"account": "_Test Cash - _TC",
|
||||
"debit": 100 * -1,
|
||||
"debit_in_account_currency": 100 * -1,
|
||||
"exchange_rate": 1,
|
||||
},
|
||||
{
|
||||
"account": "_Test Bank - _TC",
|
||||
"credit": 100 * -1,
|
||||
"credit_in_account_currency": 100 * -1,
|
||||
"exchange_rate": 1,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
jv.flags.ignore_validate = True
|
||||
jv.save()
|
||||
|
||||
self.assertEqual(len(jv.accounts), 2)
|
||||
|
||||
gl_map = jv.build_gl_map()
|
||||
|
||||
for row in gl_map:
|
||||
if row.account == "_Test Cash - _TC":
|
||||
self.assertEqual(row.debit, 100 * -1)
|
||||
self.assertEqual(row.debit_in_account_currency, 100 * -1)
|
||||
self.assertEqual(row.debit_in_transaction_currency, 100 * -1)
|
||||
|
||||
gl_map = process_gl_map(gl_map, False)
|
||||
|
||||
for row in gl_map:
|
||||
if row.account == "_Test Cash - _TC":
|
||||
self.assertEqual(row.credit, 100)
|
||||
self.assertEqual(row.credit_in_account_currency, 100)
|
||||
self.assertEqual(row.credit_in_transaction_currency, 100)
|
||||
|
||||
def test_transaction_exchange_rate_on_journals(self):
|
||||
jv = make_journal_entry("_Test Bank - _TC", "_Test Receivable USD - _TC", 100, save=False)
|
||||
jv.accounts[0].update({"debit_in_account_currency": 8500, "exchange_rate": 1})
|
||||
|
||||
@@ -1219,11 +1219,19 @@ class PaymentEntry(AccountsController):
|
||||
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
|
||||
|
||||
gle.update(
|
||||
{
|
||||
dr_or_cr: allocated_amount_in_company_currency,
|
||||
dr_or_cr + "_in_account_currency": d.allocated_amount,
|
||||
"cost_center": cost_center,
|
||||
}
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.party_account,
|
||||
"party_type": self.party_type,
|
||||
"party": self.party,
|
||||
"against": against_account,
|
||||
"account_currency": self.party_account_currency,
|
||||
"cost_center": cost_center,
|
||||
dr_or_cr + "_in_account_currency": d.allocated_amount,
|
||||
dr_or_cr: allocated_amount_in_company_currency,
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
if self.book_advance_payments_in_separate_party_account:
|
||||
@@ -1746,7 +1754,7 @@ class PaymentEntry(AccountsController):
|
||||
if paid_amount > total_negative_outstanding:
|
||||
if total_negative_outstanding == 0:
|
||||
frappe.msgprint(
|
||||
_("Cannot {0} from {2} without any negative outstanding invoice").format(
|
||||
_("Cannot {0} from {1} without any negative outstanding invoice").format(
|
||||
self.payment_type,
|
||||
self.party_type,
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ import json
|
||||
import frappe
|
||||
from frappe import _, qb
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.query_builder.functions import Abs, Sum
|
||||
from frappe.utils import flt, nowdate
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
|
||||
@@ -564,6 +564,8 @@ def make_payment_request(**args):
|
||||
# fetches existing payment request `grand_total` amount
|
||||
existing_payment_request_amount = get_existing_payment_request_amount(ref_doc.doctype, ref_doc.name)
|
||||
|
||||
existing_paid_amount = get_existing_paid_amount(ref_doc.doctype, ref_doc.name)
|
||||
|
||||
def validate_and_calculate_grand_total(grand_total, existing_payment_request_amount):
|
||||
grand_total -= existing_payment_request_amount
|
||||
if not grand_total:
|
||||
@@ -583,6 +585,15 @@ def make_payment_request(**args):
|
||||
else:
|
||||
grand_total = validate_and_calculate_grand_total(grand_total, existing_payment_request_amount)
|
||||
|
||||
if existing_paid_amount:
|
||||
if ref_doc.party_account_currency == ref_doc.currency:
|
||||
if ref_doc.conversion_rate:
|
||||
grand_total -= flt(existing_paid_amount / ref_doc.conversion_rate)
|
||||
else:
|
||||
grand_total -= flt(existing_paid_amount)
|
||||
else:
|
||||
grand_total -= flt(existing_paid_amount / ref_doc.conversion_rate)
|
||||
|
||||
if draft_payment_request:
|
||||
frappe.db.set_value(
|
||||
"Payment Request", draft_payment_request, "grand_total", grand_total, update_modified=False
|
||||
@@ -753,6 +764,27 @@ def get_existing_payment_request_amount(ref_dt, ref_dn, statuses: list | None =
|
||||
return response[0][0] if response[0] else 0
|
||||
|
||||
|
||||
def get_existing_paid_amount(doctype, name):
|
||||
PL = frappe.qb.DocType("Payment Ledger Entry")
|
||||
PER = frappe.qb.DocType("Payment Entry Reference")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(PL)
|
||||
.left_join(PER)
|
||||
.on(
|
||||
(PER.reference_doctype == PL.against_voucher_type) & (PER.reference_name == PL.against_voucher_no)
|
||||
)
|
||||
.select(Abs(Sum(PL.amount)).as_("total_paid_amount"))
|
||||
.where(PL.against_voucher_type.eq(doctype))
|
||||
.where(PL.against_voucher_no.eq(name))
|
||||
.where(PL.amount < 0)
|
||||
.where(PER.payment_request.isnull())
|
||||
)
|
||||
response = query.run()
|
||||
|
||||
return response[0][0] if response[0] else 0
|
||||
|
||||
|
||||
def get_gateway_details(args): # nosemgrep
|
||||
"""
|
||||
Return gateway and payment account of default payment gateway
|
||||
|
||||
@@ -7,6 +7,7 @@ import unittest
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template
|
||||
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
@@ -524,3 +525,21 @@ class TestPaymentRequest(FrappeTestCase):
|
||||
self.assertEqual(pr.grand_total, 1000)
|
||||
|
||||
so.load_from_db()
|
||||
|
||||
def test_partial_paid_invoice_with_payment_request(self):
|
||||
si = create_sales_invoice(currency="INR", qty=1, rate=5000)
|
||||
si.save()
|
||||
si.submit()
|
||||
|
||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
|
||||
pe.reference_no = "PAYEE0002"
|
||||
pe.reference_date = frappe.utils.nowdate()
|
||||
pe.paid_amount = 2500
|
||||
pe.references[0].allocated_amount = 2500
|
||||
pe.save()
|
||||
pe.submit()
|
||||
|
||||
si.load_from_db()
|
||||
pr = make_payment_request(dt="Sales Invoice", dn=si.name, mute_email=1)
|
||||
|
||||
self.assertEqual(pr.grand_total, si.outstanding_amount)
|
||||
|
||||
@@ -171,9 +171,7 @@ class PeriodClosingVoucher(AccountsController):
|
||||
pl_account_balances = self.get_account_balances_based_on_dimensions(report_type="Profit and Loss")
|
||||
for dimensions, account_balances in pl_account_balances.items():
|
||||
for acc, balances in account_balances.items():
|
||||
balance_in_company_currency = flt(balances.debit_in_account_currency) - flt(
|
||||
balances.credit_in_account_currency
|
||||
)
|
||||
balance_in_company_currency = flt(balances.debit) - flt(balances.credit)
|
||||
if balance_in_company_currency and acc != "balances":
|
||||
self.pl_accounts_reverse_gle.append(
|
||||
self.get_gle_for_pl_account(acc, balances, dimensions)
|
||||
|
||||
@@ -147,7 +147,7 @@ frappe.ui.form.on("POS Closing Entry", {
|
||||
frm.doc.grand_total += flt(doc.grand_total);
|
||||
frm.doc.net_total += flt(doc.net_total);
|
||||
frm.doc.total_quantity += flt(doc.total_qty);
|
||||
refresh_payments(doc, frm);
|
||||
refresh_payments(doc, frm, false);
|
||||
refresh_taxes(doc, frm);
|
||||
refresh_fields(frm);
|
||||
set_html_data(frm);
|
||||
@@ -172,7 +172,7 @@ function set_form_data(data, frm) {
|
||||
frm.doc.grand_total += flt(d.grand_total);
|
||||
frm.doc.net_total += flt(d.net_total);
|
||||
frm.doc.total_quantity += flt(d.total_qty);
|
||||
refresh_payments(d, frm);
|
||||
refresh_payments(d, frm, true);
|
||||
refresh_taxes(d, frm);
|
||||
});
|
||||
}
|
||||
@@ -186,7 +186,7 @@ function add_to_pos_transaction(d, frm) {
|
||||
});
|
||||
}
|
||||
|
||||
function refresh_payments(d, frm) {
|
||||
function refresh_payments(d, frm, is_new) {
|
||||
d.payments.forEach((p) => {
|
||||
const payment = frm.doc.payment_reconciliation.find(
|
||||
(pay) => pay.mode_of_payment === p.mode_of_payment
|
||||
@@ -196,9 +196,7 @@ function refresh_payments(d, frm) {
|
||||
}
|
||||
if (payment) {
|
||||
payment.expected_amount += flt(p.amount);
|
||||
if (payment.closing_amount === 0) {
|
||||
payment.closing_amount = payment.expected_amount;
|
||||
}
|
||||
if (is_new) payment.closing_amount = payment.expected_amount;
|
||||
payment.difference = payment.closing_amount - payment.expected_amount;
|
||||
} else {
|
||||
frm.add_child("payment_reconciliation", {
|
||||
|
||||
@@ -1137,6 +1137,45 @@ class TestPricingRule(FrappeTestCase):
|
||||
so.save()
|
||||
self.assertEqual(len(so.items), 1)
|
||||
|
||||
def test_pricing_rule_for_product_free_item_round_free_qty(self):
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
|
||||
test_record = {
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Pricing Rule",
|
||||
"apply_on": "Item Code",
|
||||
"currency": "USD",
|
||||
"items": [
|
||||
{
|
||||
"item_code": "_Test Item",
|
||||
}
|
||||
],
|
||||
"selling": 1,
|
||||
"rate": 0,
|
||||
"min_qty": 100,
|
||||
"max_qty": 0,
|
||||
"price_or_product_discount": "Product",
|
||||
"same_item": 1,
|
||||
"free_qty": 10,
|
||||
"round_free_qty": 1,
|
||||
"is_recursive": 1,
|
||||
"recurse_for": 100,
|
||||
"company": "_Test Company",
|
||||
}
|
||||
frappe.get_doc(test_record.copy()).insert()
|
||||
|
||||
# With pricing rule
|
||||
so = make_sales_order(item_code="_Test Item", qty=100)
|
||||
so.load_from_db()
|
||||
self.assertEqual(so.items[1].is_free_item, 1)
|
||||
self.assertEqual(so.items[1].item_code, "_Test Item")
|
||||
self.assertEqual(so.items[1].qty, 10)
|
||||
|
||||
so = make_sales_order(item_code="_Test Item", qty=150)
|
||||
so.load_from_db()
|
||||
self.assertEqual(so.items[1].is_free_item, 1)
|
||||
self.assertEqual(so.items[1].item_code, "_Test Item")
|
||||
self.assertEqual(so.items[1].qty, 10)
|
||||
|
||||
def test_apply_multiple_pricing_rules_for_discount_percentage_and_amount(self):
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
|
||||
|
||||
@@ -655,7 +655,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
||||
if transaction_qty:
|
||||
qty = flt(transaction_qty) * qty / pricing_rule.recurse_for
|
||||
if pricing_rule.round_free_qty:
|
||||
qty = math.floor(qty)
|
||||
qty = (flt(transaction_qty) // pricing_rule.recurse_for) * (pricing_rule.free_qty or 1)
|
||||
|
||||
if not qty:
|
||||
return
|
||||
|
||||
@@ -474,6 +474,7 @@ def send_emails(document_name, from_scheduler=False, posting_date=None):
|
||||
reference_doctype="Process Statement Of Accounts",
|
||||
reference_name=document_name,
|
||||
attachments=attachments,
|
||||
expose_recipients="header",
|
||||
)
|
||||
|
||||
if doc.enable_auto_email and from_scheduler:
|
||||
|
||||
@@ -45,7 +45,7 @@ class RepostAccountingLedger(Document):
|
||||
latest_pcv = (
|
||||
frappe.db.get_all(
|
||||
"Period Closing Voucher",
|
||||
filters={"company": self.company},
|
||||
filters={"company": self.company, "docstatus": 1},
|
||||
order_by="period_end_date desc",
|
||||
pluck="period_end_date",
|
||||
limit=1,
|
||||
|
||||
@@ -741,20 +741,6 @@ frappe.ui.form.on("Sales Invoice", {
|
||||
};
|
||||
};
|
||||
|
||||
frm.set_query("company_address", function (doc) {
|
||||
if (!doc.company) {
|
||||
frappe.throw(__("Please set Company"));
|
||||
}
|
||||
|
||||
return {
|
||||
query: "frappe.contacts.doctype.address.address.address_query",
|
||||
filters: {
|
||||
link_doctype: "Company",
|
||||
link_name: doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("pos_profile", function (doc) {
|
||||
if (!doc.company) {
|
||||
frappe.throw(__("Please set Company"));
|
||||
|
||||
@@ -89,11 +89,14 @@
|
||||
"incoming_rate",
|
||||
"item_tax_rate",
|
||||
"actual_batch_qty",
|
||||
"actual_qty",
|
||||
"section_break_eoec",
|
||||
"serial_no",
|
||||
"column_break_ytgd",
|
||||
"batch_no",
|
||||
"available_quantity_section",
|
||||
"actual_qty",
|
||||
"column_break_ogff",
|
||||
"company_total_stock",
|
||||
"edit_references",
|
||||
"sales_order",
|
||||
"so_detail",
|
||||
@@ -675,7 +678,8 @@
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "actual_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Available Qty at Warehouse",
|
||||
"label": "Qty (Warehouse)",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "actual_qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"print_hide": 1,
|
||||
@@ -923,12 +927,30 @@
|
||||
{
|
||||
"fieldname": "column_break_ytgd",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "available_quantity_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Available Quantity"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ogff",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "company_total_stock",
|
||||
"fieldtype": "Float",
|
||||
"label": "Qty (Company)",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-10-28 15:06:40.980995",
|
||||
"modified": "2024-11-25 16:27:33.287341",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
||||
@@ -28,6 +28,7 @@ class SalesInvoiceItem(Document):
|
||||
base_rate_with_margin: DF.Currency
|
||||
batch_no: DF.Link | None
|
||||
brand: DF.Data | None
|
||||
company_total_stock: DF.Float
|
||||
conversion_factor: DF.Float
|
||||
cost_center: DF.Link
|
||||
customer_item_code: DF.Data | None
|
||||
|
||||
@@ -315,66 +315,48 @@ def check_if_in_list(gle, gl_map):
|
||||
|
||||
|
||||
def toggle_debit_credit_if_negative(gl_map):
|
||||
debit_credit_field_map = {
|
||||
"debit": "credit",
|
||||
"debit_in_account_currency": "credit_in_account_currency",
|
||||
"debit_in_transaction_currency": "credit_in_transaction_currency",
|
||||
}
|
||||
|
||||
for entry in gl_map:
|
||||
# toggle debit, credit if negative entry
|
||||
if flt(entry.debit) < 0 and flt(entry.credit) < 0 and flt(entry.debit) == flt(entry.credit):
|
||||
entry.credit *= -1
|
||||
entry.debit *= -1
|
||||
for debit_field, credit_field in debit_credit_field_map.items():
|
||||
debit = flt(entry.get(debit_field))
|
||||
credit = flt(entry.get(credit_field))
|
||||
|
||||
if (
|
||||
flt(entry.debit_in_account_currency) < 0
|
||||
and flt(entry.credit_in_account_currency) < 0
|
||||
and flt(entry.debit_in_account_currency) == flt(entry.credit_in_account_currency)
|
||||
):
|
||||
entry.credit_in_account_currency *= -1
|
||||
entry.debit_in_account_currency *= -1
|
||||
if debit < 0 and credit < 0 and debit == credit:
|
||||
debit *= -1
|
||||
credit *= -1
|
||||
|
||||
if flt(entry.debit) < 0:
|
||||
entry.credit = flt(entry.credit) - flt(entry.debit)
|
||||
entry.debit = 0.0
|
||||
if debit < 0:
|
||||
credit = credit - debit
|
||||
debit = 0.0
|
||||
|
||||
if flt(entry.debit_in_account_currency) < 0:
|
||||
entry.credit_in_account_currency = flt(entry.credit_in_account_currency) - flt(
|
||||
entry.debit_in_account_currency
|
||||
)
|
||||
entry.debit_in_account_currency = 0.0
|
||||
if credit < 0:
|
||||
debit = debit - credit
|
||||
credit = 0.0
|
||||
|
||||
if flt(entry.credit) < 0:
|
||||
entry.debit = flt(entry.debit) - flt(entry.credit)
|
||||
entry.credit = 0.0
|
||||
# update net values
|
||||
# In some scenarios net value needs to be shown in the ledger
|
||||
# This method updates net values as debit or credit
|
||||
if entry.post_net_value and debit and credit:
|
||||
if debit > credit:
|
||||
debit = debit - credit
|
||||
credit = 0.0
|
||||
|
||||
if flt(entry.credit_in_account_currency) < 0:
|
||||
entry.debit_in_account_currency = flt(entry.debit_in_account_currency) - flt(
|
||||
entry.credit_in_account_currency
|
||||
)
|
||||
entry.credit_in_account_currency = 0.0
|
||||
else:
|
||||
credit = credit - debit
|
||||
debit = 0.0
|
||||
|
||||
update_net_values(entry)
|
||||
entry[debit_field] = debit
|
||||
entry[credit_field] = credit
|
||||
|
||||
return gl_map
|
||||
|
||||
|
||||
def update_net_values(entry):
|
||||
# In some scenarios net value needs to be shown in the ledger
|
||||
# This method updates net values as debit or credit
|
||||
if entry.post_net_value and entry.debit and entry.credit:
|
||||
if entry.debit > entry.credit:
|
||||
entry.debit = entry.debit - entry.credit
|
||||
entry.debit_in_account_currency = (
|
||||
entry.debit_in_account_currency - entry.credit_in_account_currency
|
||||
)
|
||||
entry.credit = 0
|
||||
entry.credit_in_account_currency = 0
|
||||
else:
|
||||
entry.credit = entry.credit - entry.debit
|
||||
entry.credit_in_account_currency = (
|
||||
entry.credit_in_account_currency - entry.debit_in_account_currency
|
||||
)
|
||||
|
||||
entry.debit = 0
|
||||
entry.debit_in_account_currency = 0
|
||||
|
||||
|
||||
def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
|
||||
if not from_repost:
|
||||
validate_cwip_accounts(gl_map)
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Number Card",
|
||||
"document_type": "Purchase Invoice",
|
||||
"dynamic_filters_json": "[[\"Purchase Invoice\",\"company\",\"=\",\" frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
|
||||
"filters_json": "[[\"Purchase Invoice\",\"docstatus\",\"=\",\"1\",false],[\"Purchase Invoice\",\"posting_date\",\"Timespan\",\"this year\",false]]",
|
||||
"function": "Sum",
|
||||
"idx": 0,
|
||||
"is_public": 1,
|
||||
"is_standard": 1,
|
||||
"label": "Total Incoming Bills",
|
||||
"modified": "2020-07-22 13:06:46.045344",
|
||||
"modified": "2024-11-20 19:08:37.043777",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Total Incoming Bills",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Number Card",
|
||||
"document_type": "Payment Entry",
|
||||
"dynamic_filters_json": "[[\"Payment Entry\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
|
||||
"filters_json": "[[\"Payment Entry\",\"docstatus\",\"=\",\"1\",false],[\"Payment Entry\",\"posting_date\",\"Timespan\",\"this year\",false],[\"Payment Entry\",\"payment_type\",\"=\",\"Receive\",false]]",
|
||||
"function": "Sum",
|
||||
"idx": 0,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Number Card",
|
||||
"document_type": "Sales Invoice",
|
||||
"dynamic_filters_json": "[[\"Sales Invoice\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
|
||||
"filters_json": "[[\"Sales Invoice\",\"docstatus\",\"=\",\"1\",false],[\"Sales Invoice\",\"posting_date\",\"Timespan\",\"this year\",false]]",
|
||||
"function": "Sum",
|
||||
"idx": 0,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Number Card",
|
||||
"document_type": "Payment Entry",
|
||||
"dynamic_filters_json": "[[\"Payment Entry\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
|
||||
"filters_json": "[[\"Payment Entry\",\"docstatus\",\"=\",\"1\",false],[\"Payment Entry\",\"posting_date\",\"Timespan\",\"this year\",false],[\"Payment Entry\",\"payment_type\",\"=\",\"Pay\",false]]",
|
||||
"function": "Sum",
|
||||
"idx": 0,
|
||||
|
||||
@@ -1013,15 +1013,15 @@ class ReceivablePayableReport:
|
||||
|
||||
def get_columns(self):
|
||||
self.columns = []
|
||||
self.add_column("Posting Date", fieldtype="Date")
|
||||
self.add_column(_("Posting Date"), fieldtype="Date")
|
||||
self.add_column(
|
||||
label="Party Type",
|
||||
label=_("Party Type"),
|
||||
fieldname="party_type",
|
||||
fieldtype="Data",
|
||||
width=100,
|
||||
)
|
||||
self.add_column(
|
||||
label="Party",
|
||||
label=_("Party"),
|
||||
fieldname="party",
|
||||
fieldtype="Dynamic Link",
|
||||
options="party_type",
|
||||
@@ -1037,10 +1037,10 @@ class ReceivablePayableReport:
|
||||
|
||||
if self.party_naming_by == "Naming Series":
|
||||
if self.account_type == "Payable":
|
||||
label = "Supplier Name"
|
||||
label = _("Supplier Name")
|
||||
fieldname = "supplier_name"
|
||||
else:
|
||||
label = "Customer Name"
|
||||
label = _("Customer Name")
|
||||
fieldname = "customer_name"
|
||||
self.add_column(
|
||||
label=label,
|
||||
@@ -1066,7 +1066,7 @@ class ReceivablePayableReport:
|
||||
width=180,
|
||||
)
|
||||
|
||||
self.add_column(label="Due Date", fieldtype="Date")
|
||||
self.add_column(label=_("Due Date"), fieldtype="Date")
|
||||
|
||||
if self.account_type == "Payable":
|
||||
self.add_column(label=_("Bill No"), fieldname="bill_no", fieldtype="Data")
|
||||
|
||||
@@ -440,6 +440,7 @@ class GrossProfitGenerator:
|
||||
|
||||
if grouped_by_invoice:
|
||||
buying_amount = 0
|
||||
base_amount = 0
|
||||
|
||||
for row in reversed(self.si_list):
|
||||
if self.filters.get("group_by") == "Monthly":
|
||||
@@ -480,12 +481,11 @@ class GrossProfitGenerator:
|
||||
else:
|
||||
row.buying_amount = flt(self.get_buying_amount(row, row.item_code), self.currency_precision)
|
||||
|
||||
if grouped_by_invoice:
|
||||
if row.indent == 1.0:
|
||||
buying_amount += row.buying_amount
|
||||
elif row.indent == 0.0:
|
||||
row.buying_amount = buying_amount
|
||||
buying_amount = 0
|
||||
if grouped_by_invoice and row.indent == 0.0:
|
||||
row.buying_amount = buying_amount
|
||||
row.base_amount = base_amount
|
||||
buying_amount = 0
|
||||
base_amount = 0
|
||||
|
||||
# get buying rate
|
||||
if flt(row.qty):
|
||||
@@ -495,11 +495,19 @@ class GrossProfitGenerator:
|
||||
if self.is_not_invoice_row(row):
|
||||
row.buying_rate, row.base_rate = 0.0, 0.0
|
||||
|
||||
if self.is_not_invoice_row(row):
|
||||
self.update_return_invoices(row)
|
||||
|
||||
if grouped_by_invoice and row.indent == 1.0:
|
||||
buying_amount += row.buying_amount
|
||||
base_amount += row.base_amount
|
||||
|
||||
# calculate gross profit
|
||||
row.gross_profit = flt(row.base_amount - row.buying_amount, self.currency_precision)
|
||||
if row.base_amount:
|
||||
row.gross_profit_percent = flt(
|
||||
(row.gross_profit / row.base_amount) * 100.0, self.currency_precision
|
||||
(row.gross_profit / row.base_amount) * 100.0,
|
||||
self.currency_precision,
|
||||
)
|
||||
else:
|
||||
row.gross_profit_percent = 0.0
|
||||
@@ -510,33 +518,29 @@ class GrossProfitGenerator:
|
||||
if self.grouped:
|
||||
self.get_average_rate_based_on_group_by()
|
||||
|
||||
def update_return_invoices(self, row):
|
||||
if row.parent in self.returned_invoices and row.item_code in self.returned_invoices[row.parent]:
|
||||
returned_item_rows = self.returned_invoices[row.parent][row.item_code]
|
||||
for returned_item_row in returned_item_rows:
|
||||
# returned_items 'qty' should be stateful
|
||||
if returned_item_row.qty != 0:
|
||||
if row.qty >= abs(returned_item_row.qty):
|
||||
row.qty += returned_item_row.qty
|
||||
row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
|
||||
returned_item_row.qty = 0
|
||||
returned_item_row.base_amount = 0
|
||||
|
||||
else:
|
||||
row.qty = 0
|
||||
row.base_amount = 0
|
||||
returned_item_row.qty += row.qty
|
||||
returned_item_row.base_amount += row.base_amount
|
||||
|
||||
row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
|
||||
|
||||
def get_average_rate_based_on_group_by(self):
|
||||
for key in list(self.grouped):
|
||||
if self.filters.get("group_by") == "Invoice":
|
||||
for row in self.grouped[key]:
|
||||
if row.indent == 1.0:
|
||||
if (
|
||||
row.parent in self.returned_invoices
|
||||
and row.item_code in self.returned_invoices[row.parent]
|
||||
):
|
||||
returned_item_rows = self.returned_invoices[row.parent][row.item_code]
|
||||
for returned_item_row in returned_item_rows:
|
||||
# returned_items 'qty' should be stateful
|
||||
if returned_item_row.qty != 0:
|
||||
if row.qty >= abs(returned_item_row.qty):
|
||||
row.qty += returned_item_row.qty
|
||||
returned_item_row.qty = 0
|
||||
else:
|
||||
row.qty = 0
|
||||
returned_item_row.qty += row.qty
|
||||
row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
|
||||
row.buying_amount = flt(
|
||||
flt(row.qty) * flt(row.buying_rate), self.currency_precision
|
||||
)
|
||||
if flt(row.qty) or row.base_amount:
|
||||
row = self.set_average_rate(row)
|
||||
self.grouped_data.append(row)
|
||||
elif self.filters.get("group_by") == "Payment Term":
|
||||
if self.filters.get("group_by") == "Payment Term":
|
||||
for i, row in enumerate(self.grouped[key]):
|
||||
invoice_portion = 0
|
||||
|
||||
@@ -556,7 +560,7 @@ class GrossProfitGenerator:
|
||||
|
||||
new_row = self.set_average_rate(new_row)
|
||||
self.grouped_data.append(new_row)
|
||||
else:
|
||||
elif self.filters.get("group_by") != "Invoice":
|
||||
for i, row in enumerate(self.grouped[key]):
|
||||
if i == 0:
|
||||
new_row = row
|
||||
|
||||
@@ -418,12 +418,12 @@ class TestGrossProfit(FrappeTestCase):
|
||||
"item_name": self.item,
|
||||
"warehouse": "Stores - _GP",
|
||||
"qty": 0.0,
|
||||
"avg._selling_rate": 0.0,
|
||||
"avg._selling_rate": 100,
|
||||
"valuation_rate": 0.0,
|
||||
"selling_amount": -100.0,
|
||||
"selling_amount": 0.0,
|
||||
"buying_amount": 0.0,
|
||||
"gross_profit": -100.0,
|
||||
"gross_profit_%": 100.0,
|
||||
"gross_profit": 0.0,
|
||||
"gross_profit_%": 0.0,
|
||||
}
|
||||
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
|
||||
# Both items of Invoice should have '0' qty
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Number Card",
|
||||
"document_type": "Asset",
|
||||
"dynamic_filters_json": "[[\"Asset\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
|
||||
"filters_json": "[]",
|
||||
"function": "Sum",
|
||||
"idx": 0,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Number Card",
|
||||
"document_type": "Asset",
|
||||
"dynamic_filters_json": "[[\"Asset\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
|
||||
"filters_json": "[[\"Asset\",\"creation\",\"Timespan\",\"this year\",false]]",
|
||||
"function": "Count",
|
||||
"idx": 0,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Number Card",
|
||||
"document_type": "Asset",
|
||||
"dynamic_filters_json": "[[\"Asset\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
|
||||
"filters_json": "[]",
|
||||
"function": "Count",
|
||||
"idx": 0,
|
||||
|
||||
@@ -93,7 +93,7 @@ frappe.ui.form.on("Purchase Order", {
|
||||
get_materials_from_supplier: function (frm) {
|
||||
let po_details = [];
|
||||
|
||||
if (frm.doc.supplied_items && (flt(frm.doc.per_received, 2) == 100 || frm.doc.status === "Closed")) {
|
||||
if (frm.doc.supplied_items && (flt(frm.doc.per_received) == 100 || frm.doc.status === "Closed")) {
|
||||
frm.doc.supplied_items.forEach((d) => {
|
||||
if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) {
|
||||
po_details.push(d.name);
|
||||
@@ -329,8 +329,8 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
if (!["Closed", "Delivered"].includes(doc.status)) {
|
||||
if (
|
||||
this.frm.doc.status !== "Closed" &&
|
||||
flt(this.frm.doc.per_received, 2) < 100 &&
|
||||
flt(this.frm.doc.per_billed, 2) < 100
|
||||
flt(this.frm.doc.per_received) < 100 &&
|
||||
flt(this.frm.doc.per_billed) < 100
|
||||
) {
|
||||
if (!this.frm.doc.__onload || this.frm.doc.__onload.can_update_items) {
|
||||
this.frm.add_custom_button(__("Update Items"), () => {
|
||||
@@ -344,7 +344,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
}
|
||||
}
|
||||
if (this.frm.has_perm("submit")) {
|
||||
if (flt(doc.per_billed, 2) < 100 || flt(doc.per_received, 2) < 100) {
|
||||
if (flt(doc.per_billed) < 100 || flt(doc.per_received) < 100) {
|
||||
if (doc.status != "On Hold") {
|
||||
this.frm.add_custom_button(
|
||||
__("Hold"),
|
||||
@@ -383,7 +383,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
if (doc.status != "Closed") {
|
||||
if (doc.status != "On Hold") {
|
||||
if (flt(doc.per_received) < 100 && allow_receipt) {
|
||||
cur_frm.add_custom_button(
|
||||
this.frm.add_custom_button(
|
||||
__("Purchase Receipt"),
|
||||
this.make_purchase_receipt,
|
||||
__("Create")
|
||||
@@ -408,14 +408,15 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
}
|
||||
}
|
||||
}
|
||||
// Please do not add precision in the below flt function
|
||||
if (flt(doc.per_billed) < 100)
|
||||
cur_frm.add_custom_button(
|
||||
this.frm.add_custom_button(
|
||||
__("Purchase Invoice"),
|
||||
this.make_purchase_invoice,
|
||||
__("Create")
|
||||
);
|
||||
|
||||
if (flt(doc.per_billed, 2) < 100 && doc.status != "Delivered") {
|
||||
if (flt(doc.per_billed) < 100 && doc.status != "Delivered") {
|
||||
this.frm.add_custom_button(
|
||||
__("Payment"),
|
||||
() => this.make_payment_entry(),
|
||||
@@ -423,7 +424,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
);
|
||||
}
|
||||
|
||||
if (flt(doc.per_billed, 2) < 100) {
|
||||
if (flt(doc.per_billed) < 100) {
|
||||
this.frm.add_custom_button(
|
||||
__("Payment Request"),
|
||||
function () {
|
||||
|
||||
@@ -18,6 +18,7 @@ def execute(filters=None):
|
||||
|
||||
columns = get_columns(filters)
|
||||
data = get_data(filters)
|
||||
update_received_amount(data)
|
||||
|
||||
if not data:
|
||||
return [], [], None, []
|
||||
@@ -40,7 +41,6 @@ def get_data(filters):
|
||||
po = frappe.qb.DocType("Purchase Order")
|
||||
po_item = frappe.qb.DocType("Purchase Order Item")
|
||||
pi_item = frappe.qb.DocType("Purchase Invoice Item")
|
||||
pr_item = frappe.qb.DocType("Purchase Receipt Item")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(po)
|
||||
@@ -48,8 +48,6 @@ def get_data(filters):
|
||||
.on(po_item.parent == po.name)
|
||||
.left_join(pi_item)
|
||||
.on((pi_item.po_detail == po_item.name) & (pi_item.docstatus == 1))
|
||||
.left_join(pr_item)
|
||||
.on((pr_item.purchase_order_item == po_item.name) & (pr_item.docstatus == 1))
|
||||
.select(
|
||||
po.transaction_date.as_("date"),
|
||||
po_item.schedule_date.as_("required_date"),
|
||||
@@ -63,7 +61,6 @@ def get_data(filters):
|
||||
(po_item.qty - po_item.received_qty).as_("pending_qty"),
|
||||
Sum(IfNull(pi_item.qty, 0)).as_("billed_qty"),
|
||||
po_item.base_amount.as_("amount"),
|
||||
(pr_item.base_amount).as_("received_qty_amount"),
|
||||
(po_item.billed_amt * IfNull(po.conversion_rate, 1)).as_("billed_amount"),
|
||||
(po_item.base_amount - (po_item.billed_amt * IfNull(po.conversion_rate, 1))).as_(
|
||||
"pending_amount"
|
||||
@@ -95,6 +92,39 @@ def get_data(filters):
|
||||
return data
|
||||
|
||||
|
||||
def update_received_amount(data):
|
||||
pr_data = get_received_amount_data(data)
|
||||
|
||||
for row in data:
|
||||
row.received_qty_amount = flt(pr_data.get(row.name))
|
||||
|
||||
|
||||
def get_received_amount_data(data):
|
||||
pr = frappe.qb.DocType("Purchase Receipt")
|
||||
pr_item = frappe.qb.DocType("Purchase Receipt Item")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(pr)
|
||||
.inner_join(pr_item)
|
||||
.on(pr_item.parent == pr.name)
|
||||
.select(
|
||||
pr_item.purchase_order_item,
|
||||
Sum(pr_item.base_amount).as_("received_qty_amount"),
|
||||
)
|
||||
.where((pr_item.parent == pr.name) & (pr.docstatus == 1))
|
||||
.groupby(pr_item.purchase_order_item)
|
||||
)
|
||||
|
||||
query = query.where(pr_item.purchase_order_item.isin([row.name for row in data]))
|
||||
|
||||
data = query.run()
|
||||
|
||||
if not data:
|
||||
return frappe._dict()
|
||||
|
||||
return frappe._dict(data)
|
||||
|
||||
|
||||
def prepare_data(data, filters):
|
||||
completed, pending = 0, 0
|
||||
pending_field = "pending_amount"
|
||||
|
||||
@@ -1036,7 +1036,7 @@ def filter_serial_batches(parent_doc, data, row, warehouse_field=None, qty_field
|
||||
available_serial_nos.append(serial_no)
|
||||
|
||||
if available_serial_nos:
|
||||
if parent_doc.doctype in ["Purchase Invoice", "Purchase Reecipt"]:
|
||||
if parent_doc.doctype in ["Purchase Invoice", "Purchase Receipt"]:
|
||||
available_serial_nos = get_available_serial_nos(available_serial_nos, warehouse)
|
||||
|
||||
if len(available_serial_nos) > qty:
|
||||
@@ -1052,7 +1052,7 @@ def filter_serial_batches(parent_doc, data, row, warehouse_field=None, qty_field
|
||||
if batch_qty <= 0:
|
||||
continue
|
||||
|
||||
if parent_doc.doctype in ["Purchase Invoice", "Purchase Reecipt"]:
|
||||
if parent_doc.doctype in ["Purchase Invoice", "Purchase Receipt"]:
|
||||
batch_qty = get_available_batch_qty(
|
||||
parent_doc,
|
||||
batch_no,
|
||||
|
||||
@@ -21,9 +21,15 @@ class SellingController(StockController):
|
||||
|
||||
def onload(self):
|
||||
super().onload()
|
||||
if self.doctype in ("Sales Order", "Delivery Note", "Sales Invoice"):
|
||||
if self.doctype in ("Sales Order", "Delivery Note", "Sales Invoice", "Quotation"):
|
||||
for item in self.get("items") + (self.get("packed_items") or []):
|
||||
item.update(get_bin_details(item.item_code, item.warehouse, include_child_warehouses=True))
|
||||
company = self.company
|
||||
|
||||
item.update(
|
||||
get_bin_details(
|
||||
item.item_code, item.warehouse, company=company, include_child_warehouses=True
|
||||
)
|
||||
)
|
||||
|
||||
def validate(self):
|
||||
super().validate()
|
||||
|
||||
@@ -1008,11 +1008,13 @@ class StockController(AccountsController):
|
||||
def validate_qi_presence(self, row):
|
||||
"""Check if QI is present on row level. Warn on save and stop on submit if missing."""
|
||||
if not row.quality_inspection:
|
||||
msg = f"Row #{row.idx}: Quality Inspection is required for Item {frappe.bold(row.item_code)}"
|
||||
msg = _("Row #{0}: Quality Inspection is required for Item {1}").format(
|
||||
row.idx, frappe.bold(row.item_code)
|
||||
)
|
||||
if self.docstatus == 1:
|
||||
frappe.throw(_(msg), title=_("Inspection Required"), exc=QualityInspectionRequiredError)
|
||||
frappe.throw(msg, title=_("Inspection Required"), exc=QualityInspectionRequiredError)
|
||||
else:
|
||||
frappe.msgprint(_(msg), title=_("Inspection Required"), indicator="blue")
|
||||
frappe.msgprint(msg, title=_("Inspection Required"), indicator="blue")
|
||||
|
||||
def validate_qi_submission(self, row):
|
||||
"""Check if QI is submitted on row level, during submission"""
|
||||
@@ -1021,11 +1023,13 @@ class StockController(AccountsController):
|
||||
|
||||
if not qa_docstatus == 1:
|
||||
link = frappe.utils.get_link_to_form("Quality Inspection", row.quality_inspection)
|
||||
msg = f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}"
|
||||
msg = _("Row #{0}: Quality Inspection {1} is not submitted for the item: {2}").format(
|
||||
row.idx, link, row.item_code
|
||||
)
|
||||
if action == "Stop":
|
||||
frappe.throw(_(msg), title=_("Inspection Submission"), exc=QualityInspectionNotSubmittedError)
|
||||
frappe.throw(msg, title=_("Inspection Submission"), exc=QualityInspectionNotSubmittedError)
|
||||
else:
|
||||
frappe.msgprint(_(msg), alert=True, indicator="orange")
|
||||
frappe.msgprint(msg, alert=True, indicator="orange")
|
||||
|
||||
def validate_qi_rejection(self, row):
|
||||
"""Check if QI is rejected on row level, during submission"""
|
||||
@@ -1034,11 +1038,13 @@ class StockController(AccountsController):
|
||||
|
||||
if qa_status == "Rejected":
|
||||
link = frappe.utils.get_link_to_form("Quality Inspection", row.quality_inspection)
|
||||
msg = f"Row #{row.idx}: Quality Inspection {link} was rejected for item {row.item_code}"
|
||||
msg = _("Row #{0}: Quality Inspection {1} was rejected for item {2}").format(
|
||||
row.idx, link, row.item_code
|
||||
)
|
||||
if action == "Stop":
|
||||
frappe.throw(_(msg), title=_("Inspection Rejected"), exc=QualityInspectionRejectedError)
|
||||
frappe.throw(msg, title=_("Inspection Rejected"), exc=QualityInspectionRejectedError)
|
||||
else:
|
||||
frappe.msgprint(_(msg), alert=True, indicator="orange")
|
||||
frappe.msgprint(msg, alert=True, indicator="orange")
|
||||
|
||||
def update_blanket_order(self):
|
||||
blanket_orders = list(set([d.blanket_order for d in self.items if d.blanket_order]))
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Number Card",
|
||||
"document_type": "Opportunity",
|
||||
"dynamic_filters_json": "[[\"Opportunity\",\"status\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
|
||||
"dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
|
||||
"filters_json": "[[\"Opportunity\",\"company\",\"=\",null,false]]",
|
||||
"function": "Count",
|
||||
"idx": 0,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"include_item_in_manufacturing",
|
||||
"qty_section",
|
||||
"required_qty",
|
||||
"stock_uom",
|
||||
"rate",
|
||||
"amount",
|
||||
"column_break_11",
|
||||
@@ -138,11 +139,19 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Returned Qty ",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.stock_uom",
|
||||
"fieldname": "stock_uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "Stock UOM",
|
||||
"options": "UOM",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-02-11 15:45:32.318374",
|
||||
"modified": "2024-11-19 15:48:16.823384",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Work Order Item",
|
||||
@@ -153,4 +162,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ class WorkOrderItem(Document):
|
||||
item_code: DF.Link | None
|
||||
item_name: DF.Data | None
|
||||
operation: DF.Link | None
|
||||
operation_row_id: DF.Int
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
@@ -32,6 +33,7 @@ class WorkOrderItem(Document):
|
||||
required_qty: DF.Float
|
||||
returned_qty: DF.Float
|
||||
source_warehouse: DF.Link | None
|
||||
stock_uom: DF.Link | None
|
||||
transferred_qty: DF.Float
|
||||
# end: auto-generated types
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Number Card",
|
||||
"document_type": "Work Order",
|
||||
"dynamic_filters_json": "[[\"Work Order\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
|
||||
"filters_json": "[[\"Work Order\",\"status\",\"=\",\"Completed\"],[\"Work Order\",\"docstatus\",\"=\",1],[\"Work Order\",\"creation\",\"between\",[\"2020-06-08\",\"2020-07-08\"]]]",
|
||||
"function": "Count",
|
||||
"idx": 0,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Number Card",
|
||||
"document_type": "Work Order",
|
||||
"dynamic_filters_json": "[[\"Work Order\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
|
||||
"filters_json": "[[\"Work Order\",\"docstatus\",\"=\",1],[\"Work Order\",\"creation\",\"between\",[\"2020-06-08\",\"2020-07-08\"]]]",
|
||||
"function": "Count",
|
||||
"idx": 0,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Number Card",
|
||||
"document_type": "Job Card",
|
||||
"dynamic_filters_json": "[[\"Job Card\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
|
||||
"filters_json": "[[\"Job Card\",\"status\",\"!=\",\"Completed\"],[\"Job Card\",\"docstatus\",\"=\",1]]",
|
||||
"function": "Count",
|
||||
"idx": 0,
|
||||
|
||||
@@ -382,3 +382,4 @@ erpnext.patches.v15_0.link_purchase_item_to_asset_doc
|
||||
erpnext.patches.v14_0.update_currency_exchange_settings_for_frankfurter
|
||||
erpnext.patches.v15_0.update_task_assignee_email_field_in_asset_maintenance_log
|
||||
erpnext.patches.v15_0.update_sub_voucher_type_in_gl_entries
|
||||
erpnext.patches.v14_0.update_stock_uom_in_work_order_item
|
||||
|
||||
15
erpnext/patches/v14_0/update_stock_uom_in_work_order_item.py
Normal file
15
erpnext/patches/v14_0/update_stock_uom_in_work_order_item.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.db.sql(
|
||||
"""
|
||||
UPDATE
|
||||
`tabWork Order Item`, `tabItem`
|
||||
SET
|
||||
`tabWork Order Item`.stock_uom = `tabItem`.stock_uom
|
||||
WHERE
|
||||
`tabWork Order Item`.item_code = `tabItem`.name
|
||||
AND `tabWork Order Item`.docstatus = 1
|
||||
"""
|
||||
)
|
||||
@@ -1904,8 +1904,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
callback: function(r) {
|
||||
if (!r.exc) {
|
||||
frappe.run_serially([
|
||||
() => me.frm.set_value("price_list_currency", r.message.parent.price_list_currency),
|
||||
() => me.frm.set_value("plc_conversion_rate", r.message.parent.plc_conversion_rate),
|
||||
() => {
|
||||
if (r.message.parent.price_list_currency)
|
||||
me.frm.set_value("price_list_currency", r.message.parent.price_list_currency);
|
||||
},
|
||||
() => {
|
||||
if (r.message.parent.plc_conversion_rate)
|
||||
me.frm.set_value("plc_conversion_rate", r.message.parent.plc_conversion_rate);
|
||||
},
|
||||
() => {
|
||||
if(args.items.length) {
|
||||
me._set_values_for_item_list(r.message.children);
|
||||
|
||||
@@ -77,9 +77,13 @@ $.extend(erpnext.queries, {
|
||||
},
|
||||
|
||||
company_address_query: function (doc) {
|
||||
if (!doc.company) {
|
||||
frappe.throw(__("Please set {0}", [__(frappe.meta.get_label(doc.doctype, "company", doc.name))]));
|
||||
}
|
||||
|
||||
return {
|
||||
query: "frappe.contacts.doctype.address.address.address_query",
|
||||
filters: { is_your_company_address: 1, link_doctype: "Company", link_name: doc.company || "" },
|
||||
filters: { link_doctype: "Company", link_name: doc.company },
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ erpnext.sales_common = {
|
||||
me.frm.set_query("customer_address", erpnext.queries.address_query);
|
||||
me.frm.set_query("shipping_address_name", erpnext.queries.address_query);
|
||||
me.frm.set_query("dispatch_address_name", erpnext.queries.dispatch_address_query);
|
||||
me.frm.set_query("company_address", erpnext.queries.company_address_query);
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder import Criterion
|
||||
from frappe.utils import get_link_to_form
|
||||
|
||||
|
||||
@@ -93,15 +94,24 @@ class ProductBundle(Document):
|
||||
def get_new_item_code(doctype, txt, searchfield, start, page_len, filters):
|
||||
product_bundles = frappe.db.get_list("Product Bundle", {"disabled": 0}, pluck="name")
|
||||
|
||||
if not searchfield or searchfield == "name":
|
||||
searchfield = frappe.get_meta("Item").get("search_fields")
|
||||
|
||||
searchfield = searchfield.split(",")
|
||||
searchfield.append("name")
|
||||
|
||||
item = frappe.qb.DocType("Item")
|
||||
query = (
|
||||
frappe.qb.from_(item)
|
||||
.select(item.item_code, item.item_name)
|
||||
.where((item.is_stock_item == 0) & (item.is_fixed_asset == 0) & (item[searchfield].like(f"%{txt}%")))
|
||||
.select(item.name, item.item_name)
|
||||
.where((item.is_stock_item == 0) & (item.is_fixed_asset == 0))
|
||||
.limit(page_len)
|
||||
.offset(start)
|
||||
)
|
||||
|
||||
if searchfield:
|
||||
query = query.where(Criterion.any([item[fieldname].like(f"%{txt}%") for fieldname in searchfield]))
|
||||
|
||||
if product_bundles:
|
||||
query = query.where(item.name.notin(product_bundles))
|
||||
|
||||
|
||||
@@ -24,20 +24,6 @@ frappe.ui.form.on("Quotation", {
|
||||
frm.set_df_property("packed_items", "cannot_add_rows", true);
|
||||
frm.set_df_property("packed_items", "cannot_delete_rows", true);
|
||||
|
||||
frm.set_query("company_address", function (doc) {
|
||||
if (!doc.company) {
|
||||
frappe.throw(__("Please set Company"));
|
||||
}
|
||||
|
||||
return {
|
||||
query: "frappe.contacts.doctype.address.address.address_query",
|
||||
filters: {
|
||||
link_doctype: "Company",
|
||||
link_name: doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("serial_and_batch_bundle", "packed_items", (doc, cdt, cdn) => {
|
||||
let row = locals[cdt][cdn];
|
||||
return {
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
"uom",
|
||||
"conversion_factor",
|
||||
"stock_qty",
|
||||
"available_quantity_section",
|
||||
"actual_qty",
|
||||
"column_break_ylrv",
|
||||
"company_total_stock",
|
||||
"section_break_16",
|
||||
"price_list_rate",
|
||||
"base_price_list_rate",
|
||||
@@ -70,7 +74,6 @@
|
||||
"prevdoc_docname",
|
||||
"item_balance",
|
||||
"projected_qty",
|
||||
"actual_qty",
|
||||
"col_break4",
|
||||
"stock_balance",
|
||||
"item_tax_rate",
|
||||
@@ -460,9 +463,10 @@
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "actual_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Actual Qty",
|
||||
"label": "Qty (Warehouse)",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
@@ -662,12 +666,31 @@
|
||||
"label": "Has Alternative Item",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "available_quantity_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Available Quantity"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ylrv",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "company_total_stock",
|
||||
"fieldtype": "Float",
|
||||
"label": "Qty (Company)",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"report_hide": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-14 18:24:24.619832",
|
||||
"modified": "2024-11-24 15:18:43.952844",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Quotation Item",
|
||||
@@ -677,4 +700,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ class QuotationItem(Document):
|
||||
blanket_order: DF.Link | None
|
||||
blanket_order_rate: DF.Currency
|
||||
brand: DF.Link | None
|
||||
company_total_stock: DF.Float
|
||||
conversion_factor: DF.Float
|
||||
customer_item_code: DF.Data | None
|
||||
description: DF.TextEditor | None
|
||||
|
||||
@@ -26,20 +26,6 @@ frappe.ui.form.on("Sales Order", {
|
||||
return doc.stock_qty <= doc.delivered_qty ? "green" : "orange";
|
||||
});
|
||||
|
||||
frm.set_query("company_address", function (doc) {
|
||||
if (!doc.company) {
|
||||
frappe.throw(__("Please set Company"));
|
||||
}
|
||||
|
||||
return {
|
||||
query: "frappe.contacts.doctype.address.address.address_query",
|
||||
filters: {
|
||||
link_doctype: "Company",
|
||||
link_name: doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("bom_no", "items", function (doc, cdt, cdn) {
|
||||
var row = locals[cdt][cdn];
|
||||
return {
|
||||
@@ -57,8 +43,8 @@ frappe.ui.form.on("Sales Order", {
|
||||
if (frm.doc.docstatus === 1) {
|
||||
if (
|
||||
frm.doc.status !== "Closed" &&
|
||||
flt(frm.doc.per_delivered, 2) < 100 &&
|
||||
flt(frm.doc.per_billed, 2) < 100 &&
|
||||
flt(frm.doc.per_delivered) < 100 &&
|
||||
flt(frm.doc.per_billed) < 100 &&
|
||||
frm.has_perm("write")
|
||||
) {
|
||||
frm.add_custom_button(__("Update Items"), () => {
|
||||
@@ -604,7 +590,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
__("Status")
|
||||
);
|
||||
|
||||
if (flt(doc.per_delivered, 2) < 100 || flt(doc.per_billed, 2) < 100) {
|
||||
if (flt(doc.per_delivered) < 100 || flt(doc.per_billed) < 100) {
|
||||
// close
|
||||
this.frm.add_custom_button(__("Close"), () => this.close_sales_order(), __("Status"));
|
||||
}
|
||||
@@ -627,7 +613,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
) && !this.frm.doc.skip_delivery_note;
|
||||
|
||||
if (this.frm.has_perm("submit")) {
|
||||
if (flt(doc.per_delivered, 2) < 100 || flt(doc.per_billed, 2) < 100) {
|
||||
if (flt(doc.per_delivered) < 100 || flt(doc.per_billed) < 100) {
|
||||
// hold
|
||||
this.frm.add_custom_button(
|
||||
__("Hold"),
|
||||
@@ -645,8 +631,8 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
|
||||
if (
|
||||
(!doc.__onload || !doc.__onload.has_reserved_stock) &&
|
||||
flt(doc.per_picked, 2) < 100 &&
|
||||
flt(doc.per_delivered, 2) < 100 &&
|
||||
flt(doc.per_picked) < 100 &&
|
||||
flt(doc.per_delivered) < 100 &&
|
||||
frappe.model.can_create("Pick List")
|
||||
) {
|
||||
this.frm.add_custom_button(
|
||||
@@ -664,7 +650,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
|
||||
// delivery note
|
||||
if (
|
||||
flt(doc.per_delivered, 2) < 100 &&
|
||||
flt(doc.per_delivered) < 100 &&
|
||||
(order_is_a_sale || order_is_a_custom_sale) &&
|
||||
allow_delivery
|
||||
) {
|
||||
@@ -686,7 +672,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
}
|
||||
|
||||
// sales invoice
|
||||
if (flt(doc.per_billed, 2) < 100 && frappe.model.can_create("Sales Invoice")) {
|
||||
if (flt(doc.per_billed) < 100 && frappe.model.can_create("Sales Invoice")) {
|
||||
this.frm.add_custom_button(
|
||||
__("Sales Invoice"),
|
||||
() => me.make_sales_invoice(),
|
||||
@@ -697,8 +683,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
// material request
|
||||
if (
|
||||
(!doc.order_type ||
|
||||
((order_is_a_sale || order_is_a_custom_sale) &&
|
||||
flt(doc.per_delivered, 2) < 100)) &&
|
||||
((order_is_a_sale || order_is_a_custom_sale) && flt(doc.per_delivered) < 100)) &&
|
||||
frappe.model.can_create("Material Request")
|
||||
) {
|
||||
this.frm.add_custom_button(
|
||||
@@ -723,7 +708,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
}
|
||||
|
||||
// maintenance
|
||||
if (flt(doc.per_delivered, 2) < 100 && (order_is_maintenance || order_is_a_custom_sale)) {
|
||||
if (flt(doc.per_delivered) < 100 && (order_is_maintenance || order_is_a_custom_sale)) {
|
||||
if (frappe.model.can_create("Maintenance Visit")) {
|
||||
this.frm.add_custom_button(
|
||||
__("Maintenance Visit"),
|
||||
@@ -741,7 +726,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
}
|
||||
|
||||
// project
|
||||
if (flt(doc.per_delivered, 2) < 100 && frappe.model.can_create("Project")) {
|
||||
if (flt(doc.per_delivered) < 100 && frappe.model.can_create("Project")) {
|
||||
this.frm.add_custom_button(__("Project"), () => this.make_project(), __("Create"));
|
||||
}
|
||||
|
||||
@@ -769,10 +754,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
}
|
||||
}
|
||||
// payment request
|
||||
if (
|
||||
flt(doc.per_billed, precision("per_billed", doc)) <
|
||||
100 + frappe.boot.sysdefaults.over_billing_allowance
|
||||
) {
|
||||
if (flt(doc.per_billed) < 100 + frappe.boot.sysdefaults.over_billing_allowance) {
|
||||
this.frm.add_custom_button(
|
||||
__("Payment Request"),
|
||||
() => this.make_payment_request(),
|
||||
|
||||
@@ -20,14 +20,14 @@ frappe.listview_settings["Sales Order"] = {
|
||||
return [__("On Hold"), "orange", "status,=,On Hold"];
|
||||
} else if (doc.status === "Completed") {
|
||||
return [__("Completed"), "green", "status,=,Completed"];
|
||||
} else if (!doc.skip_delivery_note && flt(doc.per_delivered, 2) < 100) {
|
||||
} else if (!doc.skip_delivery_note && flt(doc.per_delivered) < 100) {
|
||||
if (frappe.datetime.get_diff(doc.delivery_date) < 0) {
|
||||
// not delivered & overdue
|
||||
return [__("Overdue"), "red", "per_delivered,<,100|delivery_date,<,Today|status,!=,Closed"];
|
||||
} else if (flt(doc.grand_total) === 0) {
|
||||
// not delivered (zeroount order)
|
||||
return [__("To Deliver"), "orange", "per_delivered,<,100|grand_total,=,0|status,!=,Closed"];
|
||||
} else if (flt(doc.per_billed, 2) < 100) {
|
||||
} else if (flt(doc.per_billed) < 100) {
|
||||
// not delivered & not billed
|
||||
return [
|
||||
__("To Deliver and Bill"),
|
||||
@@ -39,13 +39,13 @@ frappe.listview_settings["Sales Order"] = {
|
||||
return [__("To Deliver"), "orange", "per_delivered,<,100|per_billed,=,100|status,!=,Closed"];
|
||||
}
|
||||
} else if (
|
||||
flt(doc.per_delivered, 2) === 100 &&
|
||||
flt(doc.per_delivered) === 100 &&
|
||||
flt(doc.grand_total) !== 0 &&
|
||||
flt(doc.per_billed, 2) < 100
|
||||
flt(doc.per_billed) < 100
|
||||
) {
|
||||
// to bill
|
||||
return [__("To Bill"), "orange", "per_delivered,=,100|per_billed,<,100|status,!=,Closed"];
|
||||
} else if (doc.skip_delivery_note && flt(doc.per_billed, 2) < 100) {
|
||||
} else if (doc.skip_delivery_note && flt(doc.per_billed) < 100) {
|
||||
return [__("To Bill"), "orange", "per_billed,<,100|status,!=,Closed"];
|
||||
}
|
||||
},
|
||||
|
||||
@@ -78,11 +78,14 @@
|
||||
"against_blanket_order",
|
||||
"blanket_order",
|
||||
"blanket_order_rate",
|
||||
"available_quantity_section",
|
||||
"actual_qty",
|
||||
"column_break_jpky",
|
||||
"company_total_stock",
|
||||
"manufacturing_section_section",
|
||||
"bom_no",
|
||||
"planning_section",
|
||||
"projected_qty",
|
||||
"actual_qty",
|
||||
"ordered_qty",
|
||||
"planned_qty",
|
||||
"production_plan_qty",
|
||||
@@ -636,7 +639,7 @@
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "actual_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Actual Qty",
|
||||
"label": "Qty (Warehouse)",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"print_width": "70px",
|
||||
@@ -905,12 +908,30 @@
|
||||
"label": "Is Stock Item",
|
||||
"print_hide": 1,
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "company_total_stock",
|
||||
"fieldtype": "Float",
|
||||
"label": "Qty (Company)",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_jpky",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "available_quantity_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Available Quantity"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-21 18:15:56.625005",
|
||||
"modified": "2024-11-21 14:21:29.743474",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order Item",
|
||||
@@ -921,4 +942,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ class SalesOrderItem(Document):
|
||||
blanket_order_rate: DF.Currency
|
||||
bom_no: DF.Link | None
|
||||
brand: DF.Link | None
|
||||
company_total_stock: DF.Float
|
||||
conversion_factor: DF.Float
|
||||
customer_item_code: DF.Data | None
|
||||
delivered_by_supplier: DF.Check
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Number Card",
|
||||
"document_type": "Customer",
|
||||
"dynamic_filters_json": "",
|
||||
"filters_json": "[[\"Customer\",\"disabled\",\"=\",\"0\"]]",
|
||||
"function": "Count",
|
||||
"idx": 0,
|
||||
|
||||
@@ -96,6 +96,8 @@ def search_by_term(search_term, warehouse, price_list):
|
||||
def filter_result_items(result, pos_profile):
|
||||
if result and result.get("items"):
|
||||
pos_item_groups = frappe.db.get_all("POS Item Group", {"parent": pos_profile}, pluck="item_group")
|
||||
if not pos_item_groups:
|
||||
return
|
||||
result["items"] = [item for item in result.get("items") if item.get("item_group") in pos_item_groups]
|
||||
|
||||
|
||||
|
||||
@@ -556,7 +556,7 @@ erpnext.PointOfSale.Controller = class {
|
||||
const item_row_exists = !$.isEmptyObject(item_row);
|
||||
|
||||
const from_selector = field === "qty" && value === "+1";
|
||||
if (from_selector) value = flt(item_row.stock_qty) + flt(value);
|
||||
if (from_selector) value = flt(item_row.qty) + flt(value);
|
||||
|
||||
if (item_row_exists) {
|
||||
if (field === "qty") value = flt(value);
|
||||
@@ -687,7 +687,7 @@ erpnext.PointOfSale.Controller = class {
|
||||
const is_stock_item = resp[1];
|
||||
|
||||
frappe.dom.unfreeze();
|
||||
const bold_uom = item_row.uom.bold();
|
||||
const bold_uom = item_row.stock_uom.bold();
|
||||
const bold_item_code = item_row.item_code.bold();
|
||||
const bold_warehouse = warehouse.bold();
|
||||
const bold_available_qty = available_qty.toString().bold();
|
||||
|
||||
@@ -73,6 +73,11 @@ frappe.query_reports["Sales Analytics"] = {
|
||||
default: "Monthly",
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "show_aggregate_value_from_subsidiary_companies",
|
||||
label: __("Show Aggregate Value from Subsidiary Companies"),
|
||||
fieldtype: "Check",
|
||||
},
|
||||
],
|
||||
get_datatable_options(options) {
|
||||
return Object.assign(options, {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _, scrub
|
||||
from frappe.query_builder import DocType
|
||||
from frappe.query_builder.functions import IfNull
|
||||
from frappe.utils import add_days, add_to_date, flt, getdate
|
||||
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
@@ -37,7 +39,26 @@ class Analytics:
|
||||
]
|
||||
self.get_period_date_ranges()
|
||||
|
||||
def update_company_list_for_parent_company(self):
|
||||
company_list = [self.filters.get("company")]
|
||||
|
||||
selected_company = self.filters.get("company")
|
||||
if (
|
||||
selected_company
|
||||
and self.filters.get("show_aggregate_value_from_subsidiary_companies")
|
||||
and frappe.db.get_value("Company", selected_company, "is_group")
|
||||
):
|
||||
lft, rgt = frappe.db.get_value("Company", selected_company, ["lft", "rgt"])
|
||||
child_companies = frappe.db.get_list(
|
||||
"Company", filters={"lft": [">", lft], "rgt": ["<", rgt]}, pluck="name"
|
||||
)
|
||||
|
||||
company_list.extend(child_companies)
|
||||
|
||||
self.filters["company"] = company_list
|
||||
|
||||
def run(self):
|
||||
self.update_company_list_for_parent_company()
|
||||
self.get_columns()
|
||||
self.get_data()
|
||||
self.get_chart_data()
|
||||
@@ -123,14 +144,23 @@ class Analytics:
|
||||
else:
|
||||
value_field = "total_qty"
|
||||
|
||||
self.entries = frappe.db.sql(
|
||||
""" select s.order_type as entity, s.{value_field} as value_field, s.{date_field}
|
||||
from `tab{doctype}` s where s.docstatus = 1 and s.company = %s and s.{date_field} between %s and %s
|
||||
and ifnull(s.order_type, '') != '' order by s.order_type
|
||||
""".format(date_field=self.date_field, value_field=value_field, doctype=self.filters.doc_type),
|
||||
(self.filters.company, self.filters.from_date, self.filters.to_date),
|
||||
as_dict=1,
|
||||
)
|
||||
doctype = DocType(self.filters.doc_type)
|
||||
|
||||
self.entries = (
|
||||
frappe.qb.from_(doctype)
|
||||
.select(
|
||||
doctype.order_type.as_("entity"),
|
||||
doctype[self.date_field],
|
||||
doctype[value_field].as_("value_field"),
|
||||
)
|
||||
.where(
|
||||
(doctype.docstatus == 1)
|
||||
& (doctype.company.isin(self.filters.company))
|
||||
& (doctype[self.date_field].between(self.filters.from_date, self.filters.to_date))
|
||||
& (IfNull(doctype.order_type, "") != "")
|
||||
)
|
||||
.orderby(doctype.order_type)
|
||||
).run(as_dict=True)
|
||||
|
||||
self.get_teams()
|
||||
|
||||
@@ -152,7 +182,7 @@ class Analytics:
|
||||
fields=[entity, entity_name, value_field, self.date_field],
|
||||
filters={
|
||||
"docstatus": 1,
|
||||
"company": self.filters.company,
|
||||
"company": ["in", self.filters.company],
|
||||
self.date_field: ("between", [self.filters.from_date, self.filters.to_date]),
|
||||
},
|
||||
)
|
||||
@@ -167,16 +197,26 @@ class Analytics:
|
||||
else:
|
||||
value_field = "stock_qty"
|
||||
|
||||
self.entries = frappe.db.sql(
|
||||
"""
|
||||
select i.item_code as entity, i.item_name as entity_name, i.stock_uom, i.{value_field} as value_field, s.{date_field}
|
||||
from `tab{doctype} Item` i , `tab{doctype}` s
|
||||
where s.name = i.parent and i.docstatus = 1 and s.company = %s
|
||||
and s.{date_field} between %s and %s
|
||||
""".format(date_field=self.date_field, value_field=value_field, doctype=self.filters.doc_type),
|
||||
(self.filters.company, self.filters.from_date, self.filters.to_date),
|
||||
as_dict=1,
|
||||
)
|
||||
doctype = DocType(self.filters.doc_type)
|
||||
doctype_item = DocType(f"{self.filters.doc_type} Item")
|
||||
|
||||
self.entries = (
|
||||
frappe.qb.from_(doctype_item)
|
||||
.join(doctype)
|
||||
.on(doctype.name == doctype_item.parent)
|
||||
.select(
|
||||
doctype_item.item_code.as_("entity"),
|
||||
doctype_item.item_name.as_("entity_name"),
|
||||
doctype_item.stock_uom,
|
||||
doctype_item[value_field].as_("value_field"),
|
||||
doctype[self.date_field],
|
||||
)
|
||||
.where(
|
||||
(doctype_item.docstatus == 1)
|
||||
& (doctype.company.isin(self.filters.company))
|
||||
& (doctype[self.date_field].between(self.filters.from_date, self.filters.to_date))
|
||||
)
|
||||
).run(as_dict=True)
|
||||
|
||||
self.entity_names = {}
|
||||
for d in self.entries:
|
||||
@@ -201,7 +241,7 @@ class Analytics:
|
||||
fields=[entity_field, value_field, self.date_field],
|
||||
filters={
|
||||
"docstatus": 1,
|
||||
"company": self.filters.company,
|
||||
"company": ["in", self.filters.company],
|
||||
self.date_field: ("between", [self.filters.from_date, self.filters.to_date]),
|
||||
},
|
||||
)
|
||||
@@ -213,16 +253,24 @@ class Analytics:
|
||||
else:
|
||||
value_field = "qty"
|
||||
|
||||
self.entries = frappe.db.sql(
|
||||
f"""
|
||||
select i.item_group as entity, i.{value_field} as value_field, s.{self.date_field}
|
||||
from `tab{self.filters.doc_type} Item` i , `tab{self.filters.doc_type}` s
|
||||
where s.name = i.parent and i.docstatus = 1 and s.company = %s
|
||||
and s.{self.date_field} between %s and %s
|
||||
""",
|
||||
(self.filters.company, self.filters.from_date, self.filters.to_date),
|
||||
as_dict=1,
|
||||
)
|
||||
doctype = DocType(self.filters.doc_type)
|
||||
doctype_item = DocType(f"{self.filters.doc_type} Item")
|
||||
|
||||
self.entries = (
|
||||
frappe.qb.from_(doctype_item)
|
||||
.join(doctype)
|
||||
.on(doctype.name == doctype_item.parent)
|
||||
.select(
|
||||
doctype_item.item_group.as_("entity"),
|
||||
doctype_item[value_field].as_("value_field"),
|
||||
doctype[self.date_field],
|
||||
)
|
||||
.where(
|
||||
(doctype_item.docstatus == 1)
|
||||
& (doctype.company.isin(self.filters.company))
|
||||
& (doctype[self.date_field].between(self.filters.from_date, self.filters.to_date))
|
||||
)
|
||||
).run(as_dict=True)
|
||||
|
||||
self.get_groups()
|
||||
|
||||
@@ -239,7 +287,7 @@ class Analytics:
|
||||
fields=[entity, value_field, self.date_field],
|
||||
filters={
|
||||
"docstatus": 1,
|
||||
"company": self.filters.company,
|
||||
"company": ["in", self.filters.company],
|
||||
"project": ["!=", ""],
|
||||
self.date_field: ("between", [self.filters.from_date, self.filters.to_date]),
|
||||
},
|
||||
@@ -312,7 +360,7 @@ class Analytics:
|
||||
str(((posting_date.month - 1) // 3) + 1), str(posting_date.year)
|
||||
)
|
||||
else:
|
||||
year = get_fiscal_year(posting_date, company=self.filters.company)
|
||||
year = get_fiscal_year(posting_date, company=self.filters.company[0])
|
||||
period = str(year[0])
|
||||
return period
|
||||
|
||||
|
||||
@@ -87,16 +87,19 @@
|
||||
"column_break_rxvc",
|
||||
"batch_no",
|
||||
"available_qty_section",
|
||||
"actual_batch_qty",
|
||||
"actual_qty",
|
||||
"installed_qty",
|
||||
"item_tax_rate",
|
||||
"actual_batch_qty",
|
||||
"column_break_atna",
|
||||
"company_total_stock",
|
||||
"section_break_kejd",
|
||||
"installed_qty",
|
||||
"packed_qty",
|
||||
"column_break_fguf",
|
||||
"received_qty",
|
||||
"accounting_details_section",
|
||||
"expense_account",
|
||||
"column_break_71",
|
||||
"item_tax_rate",
|
||||
"internal_transfer_section",
|
||||
"material_request",
|
||||
"purchase_order",
|
||||
@@ -519,7 +522,7 @@
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "actual_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Available Qty at From Warehouse",
|
||||
"label": "Qty (Warehouse)",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "actual_qty",
|
||||
"oldfieldtype": "Currency",
|
||||
@@ -907,13 +910,30 @@
|
||||
{
|
||||
"fieldname": "column_break_rxvc",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "company_total_stock",
|
||||
"fieldtype": "Float",
|
||||
"label": "Qty (Company)",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_kejd",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_fguf",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-21 18:15:07.603672",
|
||||
"modified": "2024-11-21 17:37:37.441498",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note Item",
|
||||
@@ -923,4 +943,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ class DeliveryNoteItem(Document):
|
||||
batch_no: DF.Link | None
|
||||
billed_amt: DF.Currency
|
||||
brand: DF.Link | None
|
||||
company_total_stock: DF.Float
|
||||
conversion_factor: DF.Float
|
||||
cost_center: DF.Link | None
|
||||
customer_item_code: DF.Data | None
|
||||
|
||||
@@ -663,39 +663,41 @@ $.extend(erpnext.item, {
|
||||
}
|
||||
|
||||
frm.doc.attributes.forEach(function (d) {
|
||||
let p = new Promise((resolve) => {
|
||||
if (!d.numeric_values) {
|
||||
frappe
|
||||
.call({
|
||||
method: "frappe.client.get_list",
|
||||
args: {
|
||||
doctype: "Item Attribute Value",
|
||||
filters: [["parent", "=", d.attribute]],
|
||||
fields: ["attribute_value"],
|
||||
limit_page_length: 0,
|
||||
parent: "Item Attribute",
|
||||
order_by: "idx",
|
||||
},
|
||||
})
|
||||
.then((r) => {
|
||||
if (r.message) {
|
||||
attr_val_fields[d.attribute] = r.message.map(function (d) {
|
||||
return d.attribute_value;
|
||||
});
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let values = [];
|
||||
for (var i = d.from_range; i <= d.to_range; i = flt(i + d.increment, 6)) {
|
||||
values.push(i);
|
||||
if (!d.disabled) {
|
||||
let p = new Promise((resolve) => {
|
||||
if (!d.numeric_values) {
|
||||
frappe
|
||||
.call({
|
||||
method: "frappe.client.get_list",
|
||||
args: {
|
||||
doctype: "Item Attribute Value",
|
||||
filters: [["parent", "=", d.attribute]],
|
||||
fields: ["attribute_value"],
|
||||
limit_page_length: 0,
|
||||
parent: "Item Attribute",
|
||||
order_by: "idx",
|
||||
},
|
||||
})
|
||||
.then((r) => {
|
||||
if (r.message) {
|
||||
attr_val_fields[d.attribute] = r.message.map(function (d) {
|
||||
return d.attribute_value;
|
||||
});
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let values = [];
|
||||
for (var i = d.from_range; i <= d.to_range; i = flt(i + d.increment, 6)) {
|
||||
values.push(i);
|
||||
}
|
||||
attr_val_fields[d.attribute] = values;
|
||||
resolve();
|
||||
}
|
||||
attr_val_fields[d.attribute] = values;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
promises.push(p);
|
||||
promises.push(p);
|
||||
}
|
||||
}, this);
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
@@ -710,26 +712,29 @@ $.extend(erpnext.item, {
|
||||
for (var i = 0; i < frm.doc.attributes.length; i++) {
|
||||
var fieldtype, desc;
|
||||
var row = frm.doc.attributes[i];
|
||||
if (row.numeric_values) {
|
||||
fieldtype = "Float";
|
||||
desc =
|
||||
"Min Value: " +
|
||||
row.from_range +
|
||||
" , Max Value: " +
|
||||
row.to_range +
|
||||
", in Increments of: " +
|
||||
row.increment;
|
||||
} else {
|
||||
fieldtype = "Data";
|
||||
desc = "";
|
||||
|
||||
if (!row.disabled) {
|
||||
if (row.numeric_values) {
|
||||
fieldtype = "Float";
|
||||
desc =
|
||||
"Min Value: " +
|
||||
row.from_range +
|
||||
" , Max Value: " +
|
||||
row.to_range +
|
||||
", in Increments of: " +
|
||||
row.increment;
|
||||
} else {
|
||||
fieldtype = "Data";
|
||||
desc = "";
|
||||
}
|
||||
fields = fields.concat({
|
||||
label: row.attribute,
|
||||
fieldname: row.attribute,
|
||||
fieldtype: fieldtype,
|
||||
reqd: 0,
|
||||
description: desc,
|
||||
});
|
||||
}
|
||||
fields = fields.concat({
|
||||
label: row.attribute,
|
||||
fieldname: row.attribute,
|
||||
fieldtype: fieldtype,
|
||||
reqd: 0,
|
||||
description: desc,
|
||||
});
|
||||
}
|
||||
|
||||
if (frm.doc.image) {
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
"field_order": [
|
||||
"attribute_name",
|
||||
"numeric_values",
|
||||
"column_break_vbik",
|
||||
"disabled",
|
||||
"section_break_4",
|
||||
"from_range",
|
||||
"increment",
|
||||
@@ -70,15 +72,26 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "Item Attribute Values",
|
||||
"options": "Item Attribute Value"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_vbik",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-edit",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-02 12:03:02.359202",
|
||||
"modified": "2024-11-26 20:05:29.421714",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item Attribute",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -94,4 +107,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ class ItemAttribute(Document):
|
||||
from erpnext.stock.doctype.item_attribute_value.item_attribute_value import ItemAttributeValue
|
||||
|
||||
attribute_name: DF.Data
|
||||
disabled: DF.Check
|
||||
from_range: DF.Float
|
||||
increment: DF.Float
|
||||
item_attribute_values: DF.Table[ItemAttributeValue]
|
||||
@@ -47,6 +48,19 @@ class ItemAttribute(Document):
|
||||
|
||||
def on_update(self):
|
||||
self.validate_exising_items()
|
||||
self.set_enabled_disabled_in_items()
|
||||
|
||||
def set_enabled_disabled_in_items(self):
|
||||
db_value = self.get_doc_before_save()
|
||||
if not db_value or db_value.disabled != self.disabled:
|
||||
item_variant_table = frappe.qb.DocType("Item Variant Attribute")
|
||||
query = (
|
||||
frappe.qb.update(item_variant_table)
|
||||
.set(item_variant_table.disabled, self.disabled)
|
||||
.where(item_variant_table.attribute == self.name)
|
||||
)
|
||||
|
||||
query.run()
|
||||
|
||||
def validate_exising_items(self):
|
||||
"""Validate that if there are existing items with attributes, they are valid"""
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"column_break_2",
|
||||
"attribute_value",
|
||||
"numeric_values",
|
||||
"disabled",
|
||||
"section_break_4",
|
||||
"from_range",
|
||||
"increment",
|
||||
@@ -74,11 +75,18 @@
|
||||
"fieldname": "to_range",
|
||||
"fieldtype": "Float",
|
||||
"label": "To Range"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fetch_from": "attribute.disabled",
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-07-14 17:15:19.112119",
|
||||
"modified": "2024-11-26 20:10:49.873339",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item Variant Attribute",
|
||||
@@ -87,4 +95,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ class ItemVariantAttribute(Document):
|
||||
|
||||
attribute: DF.Link
|
||||
attribute_value: DF.Data | None
|
||||
disabled: DF.Check
|
||||
from_range: DF.Float
|
||||
increment: DF.Float
|
||||
numeric_values: DF.Check
|
||||
|
||||
@@ -6,7 +6,7 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.utils import cint, cstr, flt, get_number_format_info
|
||||
from frappe.utils import cint, cstr, flt, get_link_to_form, get_number_format_info
|
||||
|
||||
from erpnext.stock.doctype.quality_inspection_template.quality_inspection_template import (
|
||||
get_template_details,
|
||||
@@ -73,6 +73,27 @@ class QualityInspection(Document):
|
||||
if self.readings:
|
||||
self.inspect_and_set_status()
|
||||
|
||||
self.validate_inspection_required()
|
||||
|
||||
def validate_inspection_required(self):
|
||||
if self.reference_type in ["Purchase Receipt", "Purchase Invoice"] and not frappe.get_cached_value(
|
||||
"Item", self.item_code, "inspection_required_before_purchase"
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"'Inspection Required before Purchase' has disabled for the item {0}, no need to create the QI"
|
||||
).format(get_link_to_form("Item", self.item_code))
|
||||
)
|
||||
|
||||
if self.reference_type in ["Delivery Note", "Sales Invoice"] and not frappe.get_cached_value(
|
||||
"Item", self.item_code, "inspection_required_before_delivery"
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"'Inspection Required before Delivery' has disabled for the item {0}, no need to create the QI"
|
||||
).format(get_link_to_form("Item", self.item_code))
|
||||
)
|
||||
|
||||
def before_submit(self):
|
||||
self.validate_readings_status_mandatory()
|
||||
|
||||
|
||||
@@ -940,11 +940,12 @@ def get_batch_based_item_price(params, item_code) -> float:
|
||||
params = parse_json(params)
|
||||
|
||||
item_price = get_item_price(params, item_code, force_batch_no=True)
|
||||
|
||||
if not item_price:
|
||||
item_price = get_item_price(params, item_code, ignore_party=True, force_batch_no=True)
|
||||
|
||||
if item_price and item_price[0].uom == params.get("uom"):
|
||||
return item_price[0].price_list_rate
|
||||
if item_price and item_price[0][2] == params.get("uom"):
|
||||
return item_price[0][1]
|
||||
|
||||
return 0.0
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Number Card",
|
||||
"document_type": "Warehouse",
|
||||
"dynamic_filters_json": "[[\"Warehouse\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
|
||||
"filters_json": "[[\"Warehouse\",\"disabled\",\"=\",0]]",
|
||||
"function": "Count",
|
||||
"idx": 0,
|
||||
|
||||
Reference in New Issue
Block a user