mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-17 00:25:01 +00:00
Merge branch 'develop' into prevent-negative-repair-cost
This commit is contained in:
@@ -3,3 +3,4 @@ reviews:
|
||||
ignore_title_keywords:
|
||||
- "sync translations"
|
||||
- "update POT file"
|
||||
review_status: false
|
||||
|
||||
3
.github/workflows/patch.yml
vendored
3
.github/workflows/patch.yml
vendored
@@ -8,6 +8,9 @@ on:
|
||||
- '**.md'
|
||||
- '**.html'
|
||||
- '**.csv'
|
||||
- 'crowdin.yml'
|
||||
- '.coderabbit.yml'
|
||||
- '.mergify.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
||||
3
.github/workflows/server-tests-mariadb.yml
vendored
3
.github/workflows/server-tests-mariadb.yml
vendored
@@ -9,6 +9,9 @@ on:
|
||||
- '**.css'
|
||||
- '**.md'
|
||||
- '**.html'
|
||||
- 'crowdin.yml'
|
||||
- '.coderabbit.yml'
|
||||
- '.mergify.yml'
|
||||
schedule:
|
||||
# Run everday at midnight UTC / 5:30 IST
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
3
.github/workflows/server-tests-postgres.yml
vendored
3
.github/workflows/server-tests-postgres.yml
vendored
@@ -6,6 +6,9 @@ on:
|
||||
- '**.js'
|
||||
- '**.md'
|
||||
- '**.html'
|
||||
- 'crowdin.yml'
|
||||
- '.coderabbit.yml'
|
||||
- '.mergify.yml'
|
||||
types: [opened, labelled, synchronize, reopened]
|
||||
|
||||
concurrency:
|
||||
|
||||
@@ -32,8 +32,6 @@ repos:
|
||||
cypress/.*|
|
||||
.*node_modules.*|
|
||||
.*boilerplate.*|
|
||||
erpnext/public/js/controllers/.*|
|
||||
erpnext/templates/pages/order.js|
|
||||
erpnext/templates/includes/.*
|
||||
)$
|
||||
|
||||
|
||||
@@ -20,4 +20,4 @@ erpnext/controllers/ @ruthra-kumar @rohitwaghchaure @mihir-kandoi
|
||||
erpnext/patches/ @ruthra-kumar
|
||||
|
||||
.github/ @ruthra-kumar
|
||||
pyproject.toml @akhilnarang
|
||||
pyproject.toml @ruthra-kumar
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"accounting_dimension",
|
||||
"fieldname",
|
||||
"disabled",
|
||||
"column_break_2",
|
||||
"company",
|
||||
@@ -90,11 +91,17 @@
|
||||
"fieldname": "apply_restriction_on_values",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply restriction on dimension values"
|
||||
},
|
||||
{
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Fieldname"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:05:57.199186",
|
||||
"modified": "2025-08-08 14:13:22.203011",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounting Dimension Filter",
|
||||
@@ -139,8 +146,9 @@
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,17 +17,16 @@ class AccountingDimensionFilter(Document):
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.accounts.doctype.allowed_dimension.allowed_dimension import AllowedDimension
|
||||
from erpnext.accounts.doctype.applicable_on_account.applicable_on_account import (
|
||||
ApplicableOnAccount,
|
||||
)
|
||||
from erpnext.accounts.doctype.applicable_on_account.applicable_on_account import ApplicableOnAccount
|
||||
|
||||
accounting_dimension: DF.Literal
|
||||
accounting_dimension: DF.Literal[None]
|
||||
accounts: DF.Table[ApplicableOnAccount]
|
||||
allow_or_restrict: DF.Literal["Allow", "Restrict"]
|
||||
apply_restriction_on_values: DF.Check
|
||||
company: DF.Link
|
||||
dimensions: DF.Table[AllowedDimension]
|
||||
disabled: DF.Check
|
||||
fieldname: DF.Data | None
|
||||
# end: auto-generated types
|
||||
|
||||
def before_save(self):
|
||||
@@ -37,6 +36,10 @@ class AccountingDimensionFilter(Document):
|
||||
self.set("dimensions", [])
|
||||
|
||||
def validate(self):
|
||||
self.fieldname = frappe.db.get_value(
|
||||
"Accounting Dimension", {"document_type": self.accounting_dimension}, "fieldname"
|
||||
) or frappe.scrub(self.accounting_dimension) # scrub to handle default accounting dimension
|
||||
|
||||
self.validate_applicable_accounts()
|
||||
|
||||
def validate_applicable_accounts(self):
|
||||
@@ -71,7 +74,7 @@ def get_dimension_filter_map():
|
||||
"""
|
||||
SELECT
|
||||
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
|
||||
p.allow_or_restrict, a.is_mandatory
|
||||
p.allow_or_restrict, p.fieldname, a.is_mandatory
|
||||
FROM
|
||||
`tabApplicable On Account` a,
|
||||
`tabAccounting Dimension Filter` p
|
||||
@@ -86,8 +89,6 @@ def get_dimension_filter_map():
|
||||
dimension_filter_map = {}
|
||||
|
||||
for f in filters:
|
||||
f.fieldname = scrub(f.accounting_dimension)
|
||||
|
||||
build_map(
|
||||
dimension_filter_map,
|
||||
f.fieldname,
|
||||
|
||||
@@ -462,9 +462,8 @@ def unset_existing_data(company):
|
||||
"Sales Taxes and Charges Template",
|
||||
"Purchase Taxes and Charges Template",
|
||||
]:
|
||||
frappe.db.sql(
|
||||
f'''delete from `tab{doctype}` where `company`="%s"''' % (company) # nosec
|
||||
)
|
||||
dt = frappe.qb.DocType(doctype)
|
||||
frappe.qb.from_(dt).where(dt.company == company).delete().run()
|
||||
|
||||
|
||||
def set_default_accounts(company):
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
-> Resolves dunning automatically
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
@@ -163,43 +164,66 @@ class Dunning(AccountsController):
|
||||
]
|
||||
|
||||
|
||||
def resolve_dunning(doc, state):
|
||||
"""
|
||||
Check if all payments have been made and resolve dunning, if yes. Called
|
||||
when a Payment Entry is submitted.
|
||||
"""
|
||||
for reference in doc.references:
|
||||
# Consider partial and full payments:
|
||||
# Submitting full payment: outstanding_amount will be 0
|
||||
# Submitting 1st partial payment: outstanding_amount will be the pending installment
|
||||
# Cancelling full payment: outstanding_amount will revert to total amount
|
||||
# Cancelling last partial payment: outstanding_amount will revert to pending amount
|
||||
submit_condition = reference.outstanding_amount < reference.total_amount
|
||||
cancel_condition = reference.outstanding_amount <= reference.total_amount
|
||||
def update_linked_dunnings(doc, previous_outstanding_amount):
|
||||
if (
|
||||
doc.doctype != "Sales Invoice"
|
||||
or doc.is_return
|
||||
or previous_outstanding_amount == doc.outstanding_amount
|
||||
):
|
||||
return
|
||||
|
||||
if reference.reference_doctype == "Sales Invoice" and (
|
||||
submit_condition if doc.docstatus == 1 else cancel_condition
|
||||
):
|
||||
state = "Resolved" if doc.docstatus == 2 else "Unresolved"
|
||||
dunnings = get_linked_dunnings_as_per_state(reference.reference_name, state)
|
||||
to_resolve = doc.outstanding_amount < previous_outstanding_amount
|
||||
state = "Unresolved" if to_resolve else "Resolved"
|
||||
dunnings = get_linked_dunnings_as_per_state(doc.name, state)
|
||||
if not dunnings:
|
||||
return
|
||||
|
||||
for dunning in dunnings:
|
||||
resolve = True
|
||||
dunning = frappe.get_doc("Dunning", dunning.get("name"))
|
||||
for overdue_payment in dunning.overdue_payments:
|
||||
outstanding_inv = frappe.get_value(
|
||||
"Sales Invoice", overdue_payment.sales_invoice, "outstanding_amount"
|
||||
)
|
||||
outstanding_ps = frappe.get_value(
|
||||
"Payment Schedule", overdue_payment.payment_schedule, "outstanding"
|
||||
)
|
||||
resolve = resolve and (False if (outstanding_ps > 0 and outstanding_inv > 0) else True)
|
||||
dunnings = [frappe.get_doc("Dunning", dunning.name) for dunning in dunnings]
|
||||
invoices = set()
|
||||
payment_schedule_ids = set()
|
||||
|
||||
new_status = "Resolved" if resolve else "Unresolved"
|
||||
for dunning in dunnings:
|
||||
for overdue_payment in dunning.overdue_payments:
|
||||
invoices.add(overdue_payment.sales_invoice)
|
||||
if overdue_payment.payment_schedule:
|
||||
payment_schedule_ids.add(overdue_payment.payment_schedule)
|
||||
|
||||
if dunning.status != new_status:
|
||||
dunning.status = new_status
|
||||
dunning.save()
|
||||
invoice_outstanding_amounts = dict(
|
||||
frappe.get_all(
|
||||
"Sales Invoice",
|
||||
filters={"name": ["in", list(invoices)]},
|
||||
fields=["name", "outstanding_amount"],
|
||||
as_list=True,
|
||||
)
|
||||
)
|
||||
|
||||
ps_outstanding_amounts = (
|
||||
dict(
|
||||
frappe.get_all(
|
||||
"Payment Schedule",
|
||||
filters={"name": ["in", list(payment_schedule_ids)]},
|
||||
fields=["name", "outstanding"],
|
||||
as_list=True,
|
||||
)
|
||||
)
|
||||
if payment_schedule_ids
|
||||
else {}
|
||||
)
|
||||
|
||||
for dunning in dunnings:
|
||||
has_outstanding = False
|
||||
for overdue_payment in dunning.overdue_payments:
|
||||
invoice_outstanding = invoice_outstanding_amounts[overdue_payment.sales_invoice]
|
||||
ps_outstanding = ps_outstanding_amounts.get(overdue_payment.payment_schedule, 0)
|
||||
has_outstanding = invoice_outstanding > 0 and ps_outstanding > 0
|
||||
if has_outstanding:
|
||||
break
|
||||
|
||||
new_status = "Resolved" if not has_outstanding else "Unresolved"
|
||||
|
||||
if dunning.status != new_status:
|
||||
dunning.status = new_status
|
||||
dunning.save()
|
||||
|
||||
|
||||
def get_linked_dunnings_as_per_state(sales_invoice, state):
|
||||
|
||||
@@ -139,6 +139,64 @@ class TestDunning(IntegrationTestCase):
|
||||
self.assertEqual(sales_invoice.status, "Overdue")
|
||||
self.assertEqual(dunning.status, "Unresolved")
|
||||
|
||||
def test_dunning_resolution_from_credit_note(self):
|
||||
"""
|
||||
Test that dunning is resolved when a credit note is issued against the original invoice.
|
||||
"""
|
||||
sales_invoice = create_sales_invoice_against_cost_center(
|
||||
posting_date=add_days(today(), -10), qty=1, rate=100
|
||||
)
|
||||
dunning = create_dunning_from_sales_invoice(sales_invoice.name)
|
||||
dunning.submit()
|
||||
|
||||
self.assertEqual(dunning.status, "Unresolved")
|
||||
|
||||
credit_note = frappe.copy_doc(sales_invoice)
|
||||
credit_note.is_return = 1
|
||||
credit_note.return_against = sales_invoice.name
|
||||
credit_note.update_outstanding_for_self = 0
|
||||
|
||||
for item in credit_note.items:
|
||||
item.qty = -item.qty
|
||||
|
||||
credit_note.save()
|
||||
credit_note.submit()
|
||||
|
||||
dunning.reload()
|
||||
self.assertEqual(dunning.status, "Resolved")
|
||||
|
||||
credit_note.cancel()
|
||||
dunning.reload()
|
||||
self.assertEqual(dunning.status, "Unresolved")
|
||||
|
||||
def test_dunning_not_affected_by_standalone_credit_note(self):
|
||||
"""
|
||||
Test that dunning is NOT resolved when a credit note has update_outstanding_for_self checked.
|
||||
"""
|
||||
sales_invoice = create_sales_invoice_against_cost_center(
|
||||
posting_date=add_days(today(), -10), qty=1, rate=100
|
||||
)
|
||||
dunning = create_dunning_from_sales_invoice(sales_invoice.name)
|
||||
dunning.submit()
|
||||
|
||||
self.assertEqual(dunning.status, "Unresolved")
|
||||
|
||||
credit_note = frappe.copy_doc(sales_invoice)
|
||||
credit_note.is_return = 1
|
||||
credit_note.return_against = sales_invoice.name
|
||||
credit_note.update_outstanding_for_self = 1
|
||||
|
||||
for item in credit_note.items:
|
||||
item.qty = -item.qty
|
||||
|
||||
credit_note.save()
|
||||
|
||||
credit_note = frappe.get_doc("Sales Invoice", credit_note.name)
|
||||
credit_note.submit()
|
||||
|
||||
dunning.reload()
|
||||
self.assertEqual(dunning.status, "Unresolved")
|
||||
|
||||
|
||||
def create_dunning(overdue_days, dunning_type_name=None):
|
||||
posting_date = add_days(today(), -1 * overdue_days)
|
||||
|
||||
@@ -1796,6 +1796,14 @@ def make_inter_company_journal_entry(name, voucher_type, company):
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_reverse_journal_entry(source_name, target_doc=None):
|
||||
existing_reverse = frappe.db.exists("Journal Entry", {"reversal_of": source_name, "docstatus": 1})
|
||||
if existing_reverse:
|
||||
frappe.throw(
|
||||
_("A Reverse Journal Entry {0} already exists for this Journal Entry.").format(
|
||||
get_link_to_form("Journal Entry", existing_reverse)
|
||||
)
|
||||
)
|
||||
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
def post_process(source, target):
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import flt, today
|
||||
|
||||
|
||||
@@ -55,22 +56,30 @@ def get_loyalty_details(
|
||||
if not expiry_date:
|
||||
expiry_date = today()
|
||||
|
||||
condition = ""
|
||||
if company:
|
||||
condition = " and company=%s " % frappe.db.escape(company)
|
||||
if not include_expired_entry:
|
||||
condition += " and expiry_date>='%s' " % expiry_date
|
||||
LoyaltyPointEntry = frappe.qb.DocType("Loyalty Point Entry")
|
||||
|
||||
loyalty_point_details = frappe.db.sql(
|
||||
f"""select sum(loyalty_points) as loyalty_points,
|
||||
sum(purchase_amount) as total_spent from `tabLoyalty Point Entry`
|
||||
where customer=%s and loyalty_program=%s and posting_date <= %s
|
||||
{condition}
|
||||
group by customer""",
|
||||
(customer, loyalty_program, expiry_date),
|
||||
as_dict=1,
|
||||
query = (
|
||||
frappe.qb.from_(LoyaltyPointEntry)
|
||||
.select(
|
||||
Sum(LoyaltyPointEntry.loyalty_points).as_("loyalty_points"),
|
||||
Sum(LoyaltyPointEntry.purchase_amount).as_("total_spent"),
|
||||
)
|
||||
.where(
|
||||
(LoyaltyPointEntry.customer == customer)
|
||||
& (LoyaltyPointEntry.loyalty_program == loyalty_program)
|
||||
& (LoyaltyPointEntry.posting_date <= expiry_date)
|
||||
)
|
||||
.groupby(LoyaltyPointEntry.customer)
|
||||
)
|
||||
|
||||
if company:
|
||||
query = query.where(LoyaltyPointEntry.company == company)
|
||||
|
||||
if not include_expired_entry:
|
||||
query = query.where(LoyaltyPointEntry.expiry_date >= expiry_date)
|
||||
|
||||
loyalty_point_details = query.run(as_dict=True)
|
||||
|
||||
if loyalty_point_details:
|
||||
return loyalty_point_details[0]
|
||||
else:
|
||||
|
||||
@@ -200,9 +200,9 @@ class PaymentEntry(AccountsController):
|
||||
if self.difference_amount:
|
||||
frappe.throw(_("Difference Amount must be zero"))
|
||||
self.update_payment_requests()
|
||||
self.update_payment_schedule()
|
||||
self.make_gl_entries()
|
||||
self.update_outstanding_amounts()
|
||||
self.update_payment_schedule()
|
||||
self.set_status()
|
||||
|
||||
def validate_for_repost(self):
|
||||
@@ -303,10 +303,10 @@ class PaymentEntry(AccountsController):
|
||||
)
|
||||
super().on_cancel()
|
||||
self.update_payment_requests(cancel=True)
|
||||
self.update_payment_schedule(cancel=1)
|
||||
self.make_gl_entries(cancel=1)
|
||||
self.update_outstanding_amounts()
|
||||
self.delink_advance_entry_references()
|
||||
self.update_payment_schedule(cancel=1)
|
||||
self.set_status()
|
||||
|
||||
def update_payment_requests(self, cancel=False):
|
||||
|
||||
@@ -55,6 +55,16 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
|
||||
});
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||
|
||||
if (this.frm.doc.pos_profile) {
|
||||
frappe.db
|
||||
.get_value("POS Profile", this.frm.doc.pos_profile, "set_grand_total_to_default_mop")
|
||||
.then((r) => {
|
||||
if (!r.exc) {
|
||||
this.frm.set_default_payment = r.message.set_grand_total_to_default_mop;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onload_post_render(frm) {
|
||||
@@ -120,6 +130,7 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
|
||||
this.frm.meta.default_print_format = r.message.print_format || "";
|
||||
this.frm.doc.campaign = r.message.campaign;
|
||||
this.frm.allow_print_before_pay = r.message.allow_print_before_pay;
|
||||
this.frm.set_default_payment = r.message.set_default_payment;
|
||||
}
|
||||
this.frm.script_manager.trigger("update_stock");
|
||||
this.calculate_taxes_and_totals();
|
||||
|
||||
@@ -717,7 +717,13 @@ class POSInvoice(SalesInvoice):
|
||||
"Account", self.debit_to, "account_currency"
|
||||
)
|
||||
if not self.due_date and self.customer:
|
||||
self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company)
|
||||
self.due_date = get_due_date(
|
||||
self.posting_date,
|
||||
"Customer",
|
||||
self.customer,
|
||||
self.company,
|
||||
template_name=self.payment_terms_template,
|
||||
)
|
||||
|
||||
super(SalesInvoice, self).set_missing_values(for_validate)
|
||||
|
||||
@@ -732,6 +738,7 @@ class POSInvoice(SalesInvoice):
|
||||
"utm_campaign": profile.get("utm_campaign"),
|
||||
"utm_medium": profile.get("utm_medium"),
|
||||
"allow_print_before_pay": profile.get("allow_print_before_pay"),
|
||||
"set_default_payment": profile.get("set_grand_total_to_default_mop"),
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -174,6 +174,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.apply_on != 'Transaction'",
|
||||
"fieldname": "is_cumulative",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Cumulative"
|
||||
@@ -656,7 +657,7 @@
|
||||
"icon": "fa fa-gift",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2025-02-17 18:15:39.824639",
|
||||
"modified": "2025-08-20 11:40:07.096854",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pricing Rule",
|
||||
|
||||
@@ -93,12 +93,14 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.apply_on != 'Transaction'",
|
||||
"fieldname": "mixed_conditions",
|
||||
"fieldtype": "Check",
|
||||
"label": "Mixed Conditions"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.apply_on != 'Transaction'",
|
||||
"fieldname": "is_cumulative",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Cumulative"
|
||||
@@ -278,7 +280,7 @@
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:22.103686",
|
||||
"modified": "2025-08-20 11:48:23.231081",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Promotional Scheme",
|
||||
|
||||
@@ -343,7 +343,12 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
if not self.due_date:
|
||||
self.due_date = get_due_date(
|
||||
self.posting_date, "Supplier", self.supplier, self.company, self.bill_date
|
||||
self.posting_date,
|
||||
"Supplier",
|
||||
self.supplier,
|
||||
self.company,
|
||||
self.bill_date,
|
||||
template_name=self.payment_terms_template,
|
||||
)
|
||||
|
||||
tds_category = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
|
||||
|
||||
@@ -58,6 +58,13 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
|
||||
me.frm.script_manager.trigger("is_pos");
|
||||
me.frm.refresh_fields();
|
||||
frappe.db
|
||||
.get_value("POS Profile", this.frm.doc.pos_profile, "set_grand_total_to_default_mop")
|
||||
.then((r) => {
|
||||
if (!r.exc) {
|
||||
me.frm.set_default_payment = r.message.set_grand_total_to_default_mop;
|
||||
}
|
||||
});
|
||||
}
|
||||
erpnext.queries.setup_warehouse_query(this.frm);
|
||||
}
|
||||
@@ -512,8 +519,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
if (r.message && r.message.print_format) {
|
||||
if (r.message) {
|
||||
me.frm.pos_print_format = r.message.print_format;
|
||||
me.frm.set_default_payment = r.message.set_default_payment;
|
||||
}
|
||||
me.frm.trigger("update_stock");
|
||||
if (me.frm.doc.taxes_and_charges) {
|
||||
|
||||
@@ -764,6 +764,7 @@ class SalesInvoice(SellingController):
|
||||
"utm_campaign": pos.get("utm_campaign"),
|
||||
"utm_medium": pos.get("utm_medium"),
|
||||
"allow_print_before_pay": pos.get("allow_print_before_pay"),
|
||||
"set_default_payment": pos.get("set_grand_total_to_default_mop", 1),
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -25,17 +25,25 @@ def get_group_by_asset_category_data(filters):
|
||||
|
||||
asset_categories = get_asset_categories_for_grouped_by_category(filters)
|
||||
assets = get_assets_for_grouped_by_category(filters)
|
||||
asset_value_adjustment_map = get_asset_value_adjustment_map_by_category(filters)
|
||||
|
||||
for asset_category in asset_categories:
|
||||
row = frappe._dict()
|
||||
row.update(asset_category)
|
||||
|
||||
adjustments = asset_value_adjustment_map.get(asset_category.get("asset_category"), {})
|
||||
row.adjustment_before_from_date = flt(adjustments.get("adjustment_before_from_date", 0))
|
||||
row.adjustment_till_to_date = flt(adjustments.get("adjustment_till_to_date", 0))
|
||||
row.adjustment_during_period = row.adjustment_till_to_date - row.adjustment_before_from_date
|
||||
|
||||
row.value_as_on_from_date += row.adjustment_before_from_date
|
||||
row.value_as_on_to_date = (
|
||||
flt(row.value_as_on_from_date)
|
||||
+ flt(row.value_of_new_purchase)
|
||||
- flt(row.value_of_sold_asset)
|
||||
- flt(row.value_of_scrapped_asset)
|
||||
- flt(row.value_of_capitalized_asset)
|
||||
+ flt(row.adjustment_during_period)
|
||||
)
|
||||
|
||||
row.update(
|
||||
@@ -229,26 +237,93 @@ def get_assets_for_grouped_by_category(filters):
|
||||
)
|
||||
|
||||
|
||||
def get_asset_value_adjustment_map_by_category(filters):
|
||||
asset_value_adjustments = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
a.asset_category AS asset_category,
|
||||
IFNULL(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN gle.posting_date < %(from_date)s
|
||||
AND (a.disposal_date IS NULL OR a.disposal_date >= %(from_date)s)
|
||||
THEN gle.debit - gle.credit
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0) AS value_adjustment_before_from_date,
|
||||
IFNULL(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN gle.posting_date <= %(to_date)s
|
||||
AND (a.disposal_date IS NULL OR a.disposal_date >= %(to_date)s)
|
||||
THEN gle.debit - gle.credit
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0) AS value_adjustment_till_to_date
|
||||
|
||||
FROM `tabGL Entry` gle
|
||||
JOIN `tabAsset` a ON gle.against_voucher = a.name
|
||||
JOIN `tabAsset Category Account` aca
|
||||
ON aca.parent = a.asset_category
|
||||
AND aca.company_name = %(company)s
|
||||
WHERE gle.is_cancelled = 0
|
||||
AND a.docstatus = 1
|
||||
AND a.company = %(company)s
|
||||
AND a.purchase_date <= %(to_date)s
|
||||
AND gle.account = aca.fixed_asset_account
|
||||
GROUP BY a.asset_category
|
||||
""",
|
||||
{"from_date": filters.from_date, "to_date": filters.to_date, "company": filters.company},
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
category_value_adjustment_map = {}
|
||||
|
||||
for r in asset_value_adjustments:
|
||||
category_value_adjustment_map[r["asset_category"]] = {
|
||||
"adjustment_before_from_date": flt(r.get("value_adjustment_before_from_date", 0)),
|
||||
"adjustment_till_to_date": flt(r.get("value_adjustment_till_to_date", 0)),
|
||||
}
|
||||
|
||||
return category_value_adjustment_map
|
||||
|
||||
|
||||
def get_group_by_asset_data(filters):
|
||||
data = []
|
||||
|
||||
asset_details = get_asset_details_for_grouped_by_category(filters)
|
||||
assets = get_assets_for_grouped_by_asset(filters)
|
||||
asset_value_adjustment_map = get_asset_value_adjustment_map(filters)
|
||||
|
||||
for asset_detail in asset_details:
|
||||
row = frappe._dict()
|
||||
row.update(asset_detail)
|
||||
|
||||
row.update(next(asset for asset in assets if asset["asset"] == asset_detail.get("name", "")))
|
||||
adjustments = asset_value_adjustment_map.get(
|
||||
asset_detail.get("name", ""),
|
||||
{
|
||||
"adjustment_before_from_date": 0.0,
|
||||
"adjustment_till_to_date": 0.0,
|
||||
},
|
||||
)
|
||||
row.adjustment_before_from_date = adjustments["adjustment_before_from_date"]
|
||||
row.adjustment_till_to_date = adjustments["adjustment_till_to_date"]
|
||||
row.adjustment_during_period = flt(row.adjustment_till_to_date) - flt(row.adjustment_before_from_date)
|
||||
|
||||
row.value_as_on_from_date += row.adjustment_before_from_date
|
||||
|
||||
row.value_as_on_to_date = (
|
||||
flt(row.value_as_on_from_date)
|
||||
+ flt(row.value_of_new_purchase)
|
||||
- flt(row.value_of_sold_asset)
|
||||
- flt(row.value_of_scrapped_asset)
|
||||
- flt(row.value_of_capitalized_asset)
|
||||
+ flt(row.adjustment_during_period)
|
||||
)
|
||||
|
||||
row.update(next(asset for asset in assets if asset["asset"] == asset_detail.get("name", "")))
|
||||
|
||||
row.accumulated_depreciation_as_on_to_date = (
|
||||
flt(row.accumulated_depreciation_as_on_from_date)
|
||||
+ flt(row.depreciation_amount_during_the_period)
|
||||
@@ -432,6 +507,59 @@ def get_assets_for_grouped_by_asset(filters):
|
||||
)
|
||||
|
||||
|
||||
def get_asset_value_adjustment_map(filters):
|
||||
asset_with_value_adjustments = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
a.name AS asset,
|
||||
IFNULL(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN gle.posting_date < %(from_date)s
|
||||
AND (a.disposal_date IS NULL OR a.disposal_date >= %(from_date)s)
|
||||
THEN gle.debit - gle.credit
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0) AS value_adjustment_before_from_date,
|
||||
IFNULL(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN gle.posting_date <= %(to_date)s
|
||||
AND (a.disposal_date IS NULL OR a.disposal_date >= %(to_date)s)
|
||||
THEN gle.debit - gle.credit
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0) AS value_adjustment_till_to_date
|
||||
|
||||
FROM `tabGL Entry` gle
|
||||
JOIN `tabAsset` a ON gle.against_voucher = a.name
|
||||
JOIN `tabAsset Category Account` aca
|
||||
ON aca.parent = a.asset_category
|
||||
AND aca.company_name = %(company)s
|
||||
WHERE gle.is_cancelled = 0
|
||||
AND a.docstatus = 1
|
||||
AND a.company = %(company)s
|
||||
AND a.purchase_date <= %(to_date)s
|
||||
AND gle.account = aca.fixed_asset_account
|
||||
GROUP BY a.name
|
||||
""",
|
||||
{"from_date": filters.from_date, "to_date": filters.to_date, "company": filters.company},
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
asset_value_adjustment_map = {}
|
||||
|
||||
for r in asset_with_value_adjustments:
|
||||
asset_value_adjustment_map[r["asset"]] = {
|
||||
"adjustment_before_from_date": flt(r.get("value_adjustment_before_from_date", 0)),
|
||||
"adjustment_till_to_date": flt(r.get("value_adjustment_till_to_date", 0)),
|
||||
}
|
||||
|
||||
return asset_value_adjustment_map
|
||||
|
||||
|
||||
def get_columns(filters):
|
||||
columns = []
|
||||
|
||||
|
||||
@@ -1,29 +1,34 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"apply_user_permissions": 1,
|
||||
"creation": "2013-12-06 13:22:23",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 3,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2017-02-24 20:17:51.995451",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "General Ledger",
|
||||
"owner": "Administrator",
|
||||
"ref_doctype": "GL Entry",
|
||||
"report_name": "General Ledger",
|
||||
"report_type": "Script Report",
|
||||
"add_total_row": 1,
|
||||
"add_translate_data": 0,
|
||||
"columns": [],
|
||||
"creation": "2013-12-06 13:22:23",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 3,
|
||||
"is_standard": "Yes",
|
||||
"letterhead": null,
|
||||
"modified": "2025-08-13 12:47:27.645023",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "General Ledger",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "GL Entry",
|
||||
"report_name": "General Ledger",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
},
|
||||
{
|
||||
"role": "Auditor"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"timeout": 0
|
||||
}
|
||||
|
||||
@@ -356,7 +356,13 @@ def apply_conditions(query, si, sii, sip, filters, additional_conditions=None):
|
||||
query = query.where(si.posting_date <= filters.get("to_date"))
|
||||
|
||||
if filters.get("mode_of_payment"):
|
||||
query = query.where(sip.mode_of_payment == filters.get("mode_of_payment"))
|
||||
subquery = (
|
||||
frappe.qb.from_(sip)
|
||||
.select(sip.parent)
|
||||
.where(sip.mode_of_payment == filters.get("mode_of_payment"))
|
||||
.groupby(sip.parent)
|
||||
)
|
||||
query = query.where(si.name.isin(subquery))
|
||||
|
||||
if filters.get("warehouse"):
|
||||
if frappe.db.get_value("Warehouse", filters.get("warehouse"), "is_group"):
|
||||
@@ -425,8 +431,6 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||
frappe.qb.from_(si)
|
||||
.join(sii)
|
||||
.on(si.name == sii.parent)
|
||||
.left_join(sip)
|
||||
.on(sip.parent == si.name)
|
||||
.left_join(item)
|
||||
.on(sii.item_code == item.name)
|
||||
.select(
|
||||
@@ -466,7 +470,6 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||
si.update_stock,
|
||||
sii.uom,
|
||||
sii.qty,
|
||||
sip.mode_of_payment,
|
||||
)
|
||||
.where(si.docstatus == 1)
|
||||
.where(sii.parenttype == doctype)
|
||||
|
||||
@@ -1940,6 +1940,8 @@ def create_payment_ledger_entry(
|
||||
|
||||
|
||||
def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, party):
|
||||
from erpnext.accounts.doctype.dunning.dunning import update_linked_dunnings
|
||||
|
||||
if not voucher_type or not voucher_no:
|
||||
return
|
||||
|
||||
@@ -1969,6 +1971,7 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa
|
||||
|
||||
outstanding = voucher_outstanding[0]
|
||||
ref_doc = frappe.get_lazy_doc(voucher_type, voucher_no)
|
||||
previous_outstanding_amount = ref_doc.outstanding_amount
|
||||
outstanding_amount = flt(
|
||||
outstanding["outstanding_in_account_currency"], ref_doc.precision("outstanding_amount")
|
||||
)
|
||||
@@ -1982,6 +1985,7 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa
|
||||
outstanding_amount,
|
||||
)
|
||||
|
||||
update_linked_dunnings(ref_doc, previous_outstanding_amount)
|
||||
ref_doc.set_status(update=True)
|
||||
ref_doc.notify_update()
|
||||
|
||||
|
||||
@@ -1108,7 +1108,7 @@ def get_asset_account(account_name, asset=None, asset_category=None, company=Non
|
||||
def make_journal_entry(asset_name):
|
||||
asset = frappe.get_doc("Asset", asset_name)
|
||||
(
|
||||
_,
|
||||
fixed_asset_account,
|
||||
accumulated_depreciation_account,
|
||||
depreciation_expense_account,
|
||||
) = get_depreciation_accounts(asset.asset_category, asset.company)
|
||||
|
||||
@@ -627,6 +627,9 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
docstatus: 1,
|
||||
status: ["not in", ["Stopped", "Expired"]],
|
||||
},
|
||||
allow_child_item_selection: true,
|
||||
child_fieldname: "items",
|
||||
child_columns: ["item_code", "item_name", "qty", "rate", "amount"],
|
||||
});
|
||||
},
|
||||
__("Get Items From")
|
||||
|
||||
@@ -9,6 +9,7 @@ from frappe import _
|
||||
from frappe.core.doctype.communication.email import make
|
||||
from frappe.desk.form.load import get_attachments
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.query_builder import Order
|
||||
from frappe.utils import get_url
|
||||
from frappe.utils.print_format import download_pdf
|
||||
from frappe.utils.user import get_user_fullname
|
||||
@@ -582,35 +583,32 @@ def get_supplier_tag():
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filters):
|
||||
conditions = ""
|
||||
if txt:
|
||||
conditions += "and rfq.name like '%%" + txt + "%%' "
|
||||
rfq = frappe.qb.DocType("Request for Quotation")
|
||||
rfq_supplier = frappe.qb.DocType("Request for Quotation Supplier")
|
||||
|
||||
if filters.get("transaction_date"):
|
||||
conditions += "and rfq.transaction_date = '{}'".format(filters.get("transaction_date"))
|
||||
|
||||
rfq_data = frappe.db.sql(
|
||||
f"""
|
||||
select
|
||||
distinct rfq.name, rfq.transaction_date,
|
||||
rfq.company
|
||||
from
|
||||
`tabRequest for Quotation` rfq, `tabRequest for Quotation Supplier` rfq_supplier
|
||||
where
|
||||
rfq.name = rfq_supplier.parent
|
||||
and rfq_supplier.supplier = %(supplier)s
|
||||
and rfq.docstatus = 1
|
||||
and rfq.company = %(company)s
|
||||
{conditions}
|
||||
order by rfq.transaction_date ASC
|
||||
limit %(page_len)s offset %(start)s """,
|
||||
{
|
||||
"page_len": page_len,
|
||||
"start": start,
|
||||
"company": filters.get("company"),
|
||||
"supplier": filters.get("supplier"),
|
||||
},
|
||||
as_dict=1,
|
||||
query = (
|
||||
frappe.qb.from_(rfq)
|
||||
.from_(rfq_supplier)
|
||||
.select(rfq.name)
|
||||
.distinct()
|
||||
.select(rfq.transaction_date, rfq.company)
|
||||
.where(
|
||||
(rfq.name == rfq_supplier.parent)
|
||||
& (rfq_supplier.supplier == filters.get("supplier"))
|
||||
& (rfq.docstatus == 1)
|
||||
& (rfq.company == filters.get("company"))
|
||||
)
|
||||
.orderby(rfq.transaction_date, order=Order.asc)
|
||||
.limit(page_len)
|
||||
.offset(start)
|
||||
)
|
||||
|
||||
if txt:
|
||||
query = query.where(rfq.name.like(f"%%{txt}%%"))
|
||||
|
||||
if filters.get("transaction_date"):
|
||||
query = query.where(rfq.transaction_date == filters.get("transaction_date"))
|
||||
|
||||
rfq_data = query.run(as_dict=1)
|
||||
|
||||
return rfq_data
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
@@ -235,7 +237,12 @@ def get_list_context(context=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_order(source_name, target_doc=None):
|
||||
def make_purchase_order(source_name, target_doc=None, args=None):
|
||||
if args is None:
|
||||
args = {}
|
||||
if isinstance(args, str):
|
||||
args = json.loads(args)
|
||||
|
||||
def set_missing_values(source, target):
|
||||
target.run_method("set_missing_values")
|
||||
target.run_method("get_schedule_dates")
|
||||
@@ -244,6 +251,11 @@ def make_purchase_order(source_name, target_doc=None):
|
||||
def update_item(obj, target, source_parent):
|
||||
target.stock_qty = flt(obj.qty) * flt(obj.conversion_factor)
|
||||
|
||||
def select_item(d):
|
||||
filtered_items = args.get("filtered_children", [])
|
||||
child_filter = d.name in filtered_items if filtered_items else True
|
||||
return child_filter
|
||||
|
||||
doclist = get_mapped_doc(
|
||||
"Supplier Quotation",
|
||||
source_name,
|
||||
@@ -265,6 +277,7 @@ def make_purchase_order(source_name, target_doc=None):
|
||||
["sales_order", "sales_order"],
|
||||
],
|
||||
"postprocess": update_item,
|
||||
"condition": select_item,
|
||||
},
|
||||
"Purchase Taxes and Charges": {
|
||||
"doctype": "Purchase Taxes and Charges",
|
||||
|
||||
@@ -588,21 +588,27 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql(
|
||||
"""select distinct bo.name, bo.blanket_order_type, bo.to_date
|
||||
from `tabBlanket Order` bo, `tabBlanket Order Item` boi
|
||||
where
|
||||
boi.parent = bo.name
|
||||
and boi.item_code = {item_code}
|
||||
and bo.blanket_order_type = '{blanket_order_type}'
|
||||
and bo.company = {company}
|
||||
and bo.docstatus = 1""".format(
|
||||
item_code=frappe.db.escape(filters.get("item")),
|
||||
blanket_order_type=filters.get("blanket_order_type"),
|
||||
company=frappe.db.escape(filters.get("company")),
|
||||
bo = frappe.qb.DocType("Blanket Order")
|
||||
bo_item = frappe.qb.DocType("Blanket Order Item")
|
||||
|
||||
blanket_orders = (
|
||||
frappe.qb.from_(bo)
|
||||
.from_(bo_item)
|
||||
.select(bo.name)
|
||||
.distinct()
|
||||
.select(bo.blanket_order_type, bo.to_date)
|
||||
.where(
|
||||
(bo_item.parent == bo.name)
|
||||
& (bo_item.item_code == filters.get("item"))
|
||||
& (bo.blanket_order_type == filters.get("blanket_order_type"))
|
||||
& (bo.company == filters.get("company"))
|
||||
& (bo.docstatus == 1)
|
||||
)
|
||||
.run()
|
||||
)
|
||||
|
||||
return blanket_orders
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
@@ -620,7 +626,7 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
if filters.get("company"):
|
||||
condition += "and tabAccount.company = %(company)s"
|
||||
|
||||
condition += f"and tabAccount.disabled = {filters.get('disabled', 0)}"
|
||||
condition += " and tabAccount.disabled = %(disabled)s"
|
||||
|
||||
return frappe.db.sql(
|
||||
f"""select tabAccount.name from `tabAccount`
|
||||
@@ -630,7 +636,11 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
and tabAccount.`{searchfield}` LIKE %(txt)s
|
||||
{condition} {get_match_cond(doctype)}
|
||||
order by idx desc, name""",
|
||||
{"txt": "%" + txt + "%", "company": filters.get("company", "")},
|
||||
{
|
||||
"txt": "%" + txt + "%",
|
||||
"company": filters.get("company", ""),
|
||||
"disabled": cint(filters.get("disabled", 0)),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ status_map = {
|
||||
["Pending", "eval:self.status != 'Stopped' and self.per_ordered == 0 and self.docstatus == 1"],
|
||||
[
|
||||
"Ordered",
|
||||
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'",
|
||||
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type in ['Purchase', 'Manufacture']",
|
||||
],
|
||||
[
|
||||
"Transferred",
|
||||
@@ -142,10 +142,6 @@ status_map = {
|
||||
"Partially Ordered",
|
||||
"eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1 and self.material_request_type != 'Material Transfer'",
|
||||
],
|
||||
[
|
||||
"Manufactured",
|
||||
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'",
|
||||
],
|
||||
],
|
||||
"POS Opening Entry": [
|
||||
["Draft", None],
|
||||
|
||||
@@ -1963,6 +1963,7 @@ def make_bundle_for_material_transfer(**kwargs):
|
||||
|
||||
row.warehouse = kwargs.warehouse
|
||||
|
||||
bundle_doc.set_incoming_rate()
|
||||
bundle_doc.calculate_qty_and_amount()
|
||||
bundle_doc.flags.ignore_permissions = True
|
||||
bundle_doc.flags.ignore_validate = True
|
||||
|
||||
@@ -360,7 +360,9 @@ doc_events = {
|
||||
"erpnext.regional.create_transaction_log",
|
||||
"erpnext.regional.italy.utils.sales_invoice_on_submit",
|
||||
],
|
||||
"on_cancel": ["erpnext.regional.italy.utils.sales_invoice_on_cancel"],
|
||||
"on_cancel": [
|
||||
"erpnext.regional.italy.utils.sales_invoice_on_cancel",
|
||||
],
|
||||
"on_trash": "erpnext.regional.check_deletion_permission",
|
||||
},
|
||||
"Purchase Invoice": {
|
||||
@@ -372,9 +374,7 @@ doc_events = {
|
||||
"Payment Entry": {
|
||||
"on_submit": [
|
||||
"erpnext.regional.create_transaction_log",
|
||||
"erpnext.accounts.doctype.dunning.dunning.resolve_dunning",
|
||||
],
|
||||
"on_cancel": ["erpnext.accounts.doctype.dunning.dunning.resolve_dunning"],
|
||||
"on_trash": "erpnext.regional.check_deletion_permission",
|
||||
},
|
||||
"Address": {
|
||||
|
||||
1331
erpnext/locale/ar.po
1331
erpnext/locale/ar.po
File diff suppressed because it is too large
Load Diff
1333
erpnext/locale/bs.po
1333
erpnext/locale/bs.po
File diff suppressed because it is too large
Load Diff
1329
erpnext/locale/cs.po
1329
erpnext/locale/cs.po
File diff suppressed because it is too large
Load Diff
1331
erpnext/locale/de.po
1331
erpnext/locale/de.po
File diff suppressed because it is too large
Load Diff
1331
erpnext/locale/eo.po
1331
erpnext/locale/eo.po
File diff suppressed because it is too large
Load Diff
1331
erpnext/locale/es.po
1331
erpnext/locale/es.po
File diff suppressed because it is too large
Load Diff
1373
erpnext/locale/fa.po
1373
erpnext/locale/fa.po
File diff suppressed because it is too large
Load Diff
1331
erpnext/locale/fr.po
1331
erpnext/locale/fr.po
File diff suppressed because it is too large
Load Diff
1331
erpnext/locale/hr.po
1331
erpnext/locale/hr.po
File diff suppressed because it is too large
Load Diff
1329
erpnext/locale/hu.po
1329
erpnext/locale/hu.po
File diff suppressed because it is too large
Load Diff
1331
erpnext/locale/id.po
1331
erpnext/locale/id.po
File diff suppressed because it is too large
Load Diff
1333
erpnext/locale/it.po
1333
erpnext/locale/it.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1329
erpnext/locale/nl.po
1329
erpnext/locale/nl.po
File diff suppressed because it is too large
Load Diff
1331
erpnext/locale/pl.po
1331
erpnext/locale/pl.po
File diff suppressed because it is too large
Load Diff
1329
erpnext/locale/pt.po
1329
erpnext/locale/pt.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1329
erpnext/locale/ru.po
1329
erpnext/locale/ru.po
File diff suppressed because it is too large
Load Diff
1331
erpnext/locale/sr.po
1331
erpnext/locale/sr.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1331
erpnext/locale/sv.po
1331
erpnext/locale/sv.po
File diff suppressed because it is too large
Load Diff
1331
erpnext/locale/th.po
1331
erpnext/locale/th.po
File diff suppressed because it is too large
Load Diff
1331
erpnext/locale/tr.po
1331
erpnext/locale/tr.po
File diff suppressed because it is too large
Load Diff
1329
erpnext/locale/vi.po
1329
erpnext/locale/vi.po
File diff suppressed because it is too large
Load Diff
1331
erpnext/locale/zh.po
1331
erpnext/locale/zh.po
File diff suppressed because it is too large
Load Diff
@@ -1421,11 +1421,11 @@ def add_additional_cost(stock_entry, work_order):
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
expecnse_account = (
|
||||
expense_account = (
|
||||
company_account.default_operating_cost_account or company_account.default_expense_account
|
||||
)
|
||||
add_non_stock_items_cost(stock_entry, work_order, expecnse_account)
|
||||
add_operations_cost(stock_entry, work_order, expecnse_account)
|
||||
add_non_stock_items_cost(stock_entry, work_order, expense_account)
|
||||
add_operations_cost(stock_entry, work_order, expense_account)
|
||||
|
||||
|
||||
def add_non_stock_items_cost(stock_entry, work_order, expense_account):
|
||||
@@ -1460,21 +1460,74 @@ def add_non_stock_items_cost(stock_entry, work_order, expense_account):
|
||||
)
|
||||
|
||||
|
||||
def add_operating_cost_component_wise(
|
||||
stock_entry, work_order=None, operating_cost_per_unit=None, op_expense_account=None
|
||||
):
|
||||
if not work_order:
|
||||
return False
|
||||
|
||||
cost_added = False
|
||||
for row in work_order.operations:
|
||||
workstation_cost = frappe.get_all(
|
||||
"Workstation Cost",
|
||||
fields=["operating_component", "operating_cost"],
|
||||
filters={
|
||||
"parent": row.workstation,
|
||||
"parenttype": "Workstation",
|
||||
},
|
||||
)
|
||||
|
||||
for wc in workstation_cost:
|
||||
expense_account = get_component_account(wc.operating_component) or op_expense_account
|
||||
actual_cp_operating_cost = flt(
|
||||
flt(wc.operating_cost) * flt(flt(row.actual_operation_time) / 60.0),
|
||||
row.precision("actual_operating_cost"),
|
||||
)
|
||||
|
||||
per_unit_cost = flt(actual_cp_operating_cost) / flt(row.completed_qty)
|
||||
|
||||
if per_unit_cost and expense_account:
|
||||
stock_entry.append(
|
||||
"additional_costs",
|
||||
{
|
||||
"expense_account": expense_account,
|
||||
"description": _("{0} Operating Cost for operation {1}").format(
|
||||
wc.operating_component, row.operation
|
||||
),
|
||||
"amount": per_unit_cost * flt(stock_entry.fg_completed_qty),
|
||||
},
|
||||
)
|
||||
|
||||
cost_added = True
|
||||
|
||||
return cost_added
|
||||
|
||||
|
||||
@frappe.request_cache
|
||||
def get_component_account(parent):
|
||||
return frappe.db.get_value("Workstation Operating Component Account", parent, "expense_account")
|
||||
|
||||
|
||||
def add_operations_cost(stock_entry, work_order=None, expense_account=None):
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry import get_operating_cost_per_unit
|
||||
|
||||
operating_cost_per_unit = get_operating_cost_per_unit(work_order, stock_entry.bom_no)
|
||||
|
||||
if operating_cost_per_unit:
|
||||
stock_entry.append(
|
||||
"additional_costs",
|
||||
{
|
||||
"expense_account": expense_account,
|
||||
"description": _("Operating Cost as per Work Order / BOM"),
|
||||
"amount": operating_cost_per_unit * flt(stock_entry.fg_completed_qty),
|
||||
},
|
||||
cost_added = add_operating_cost_component_wise(
|
||||
stock_entry, work_order, operating_cost_per_unit, expense_account
|
||||
)
|
||||
|
||||
if not cost_added:
|
||||
stock_entry.append(
|
||||
"additional_costs",
|
||||
{
|
||||
"expense_account": expense_account,
|
||||
"description": _("Operating Cost as per Work Order / BOM"),
|
||||
"amount": operating_cost_per_unit * flt(stock_entry.fg_completed_qty),
|
||||
},
|
||||
)
|
||||
|
||||
if work_order and work_order.additional_operating_cost and work_order.qty:
|
||||
additional_operating_cost_per_unit = flt(work_order.additional_operating_cost) / flt(work_order.qty)
|
||||
|
||||
|
||||
@@ -31,6 +31,16 @@ frappe.ui.form.on("Job Card", {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("operation", "time_logs", () => {
|
||||
let operations = (frm.doc.sub_operations || []).map((d) => d.sub_operation);
|
||||
return {
|
||||
filters: {
|
||||
name: ["in", operations],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.events.set_company_filters(frm, "target_warehouse");
|
||||
frm.events.set_company_filters(frm, "source_warehouse");
|
||||
frm.events.set_company_filters(frm, "wip_warehouse");
|
||||
frm.set_query("source_warehouse", "items", () => {
|
||||
@@ -184,7 +194,12 @@ frappe.ui.form.on("Job Card", {
|
||||
!frm.doc.finished_good ||
|
||||
!has_items?.length)
|
||||
) {
|
||||
if (!frm.doc.time_logs?.length) {
|
||||
let last_row = {};
|
||||
if (frm.doc.sub_operations?.length && frm.doc.time_logs?.length) {
|
||||
last_row = get_last_row(frm.doc.time_logs);
|
||||
}
|
||||
|
||||
if (!frm.doc.time_logs?.length || (frm.doc.sub_operations?.length && last_row?.to_time)) {
|
||||
frm.add_custom_button(__("Start Job"), () => {
|
||||
let from_time = frappe.datetime.now_datetime();
|
||||
if ((frm.doc.employee && !frm.doc.employee.length) || !frm.doc.employee) {
|
||||
@@ -313,7 +328,12 @@ frappe.ui.form.on("Job Card", {
|
||||
];
|
||||
|
||||
let last_completed_row = get_last_completed_row(frm.doc.time_logs);
|
||||
if (!last_completed_row || !last_completed_row.to_time) {
|
||||
let last_row = {};
|
||||
if (frm.doc.sub_operations?.length && frm.doc.time_logs?.length) {
|
||||
last_row = get_last_row(frm.doc.time_logs);
|
||||
}
|
||||
|
||||
if (!last_completed_row || !last_completed_row.to_time || !last_row.to_time) {
|
||||
fields.push({
|
||||
fieldtype: "Datetime",
|
||||
label: __("End Time"),
|
||||
@@ -758,3 +778,7 @@ function get_last_completed_row(time_logs) {
|
||||
return last_completed_row;
|
||||
}
|
||||
}
|
||||
|
||||
function get_last_row(time_logs) {
|
||||
return time_logs[time_logs.length - 1] || {};
|
||||
}
|
||||
|
||||
@@ -157,6 +157,9 @@ class JobCard(Document):
|
||||
self.validate_sequence_id()
|
||||
self.set_sub_operations()
|
||||
self.update_sub_operation_status()
|
||||
if self.sub_operations:
|
||||
self.set_total_completed_qty_from_sub_operations()
|
||||
|
||||
self.validate_work_order()
|
||||
|
||||
def on_update(self):
|
||||
@@ -280,8 +283,13 @@ class JobCard(Document):
|
||||
}
|
||||
)
|
||||
|
||||
def set_total_completed_qty_from_sub_operations(self):
|
||||
sub_op_total_qty = []
|
||||
for row in self.sub_operations:
|
||||
self.total_completed_qty += row.completed_qty
|
||||
sub_op_total_qty.append(flt(row.completed_qty))
|
||||
|
||||
if sub_op_total_qty:
|
||||
self.total_completed_qty = min(sub_op_total_qty)
|
||||
|
||||
def get_overlap_for(self, args, open_job_cards=None):
|
||||
time_logs = []
|
||||
@@ -613,7 +621,7 @@ class JobCard(Document):
|
||||
self.save()
|
||||
|
||||
def update_sub_operation_status(self):
|
||||
if not (self.sub_operations and self.time_logs):
|
||||
if not self.sub_operations:
|
||||
return
|
||||
|
||||
operation_wise_completed_time = {}
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"sub_operation",
|
||||
"completed_qty",
|
||||
"completed_time",
|
||||
"status",
|
||||
"completed_qty"
|
||||
"status"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -39,6 +39,7 @@
|
||||
{
|
||||
"fieldname": "completed_qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Completed Qty",
|
||||
"read_only": 1
|
||||
}
|
||||
@@ -46,15 +47,16 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:09:57.090298",
|
||||
"modified": "2025-08-20 21:44:43.941434",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Job Card Operation",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"columns": 3,
|
||||
"fieldname": "from_time",
|
||||
"fieldtype": "Datetime",
|
||||
"in_list_view": 1,
|
||||
@@ -58,17 +59,17 @@
|
||||
{
|
||||
"fieldname": "operation",
|
||||
"fieldtype": "Link",
|
||||
"label": "Operation",
|
||||
"in_list_view": 1,
|
||||
"label": "Sub Operation",
|
||||
"no_copy": 1,
|
||||
"options": "Operation",
|
||||
"read_only": 1
|
||||
"options": "Operation"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-04 15:47:11.748937",
|
||||
"modified": "2025-08-20 21:49:59.084876",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Job Card Time Log",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
|
||||
# See license.txt
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
|
||||
@@ -75,14 +76,22 @@ class TestWorkstation(IntegrationTestCase):
|
||||
bom_doc = setup_bom(item_code="_Testing Item", routing=routing_doc.name, currency="INR")
|
||||
w1 = frappe.get_doc("Workstation", "_Test Workstation A")
|
||||
# resets values
|
||||
w1.hour_rate_rent = 300
|
||||
w1.hour_rate_labour = 0
|
||||
for row in w1.workstation_costs:
|
||||
if row.operating_component == _("Rent"):
|
||||
row.operating_cost = 300
|
||||
break
|
||||
|
||||
w1.save()
|
||||
bom_doc.update_cost()
|
||||
bom_doc.reload()
|
||||
self.assertEqual(w1.hour_rate, 300)
|
||||
self.assertEqual(bom_doc.operations[0].hour_rate, 300)
|
||||
w1.hour_rate_rent = 250
|
||||
|
||||
for row in w1.workstation_costs:
|
||||
if row.operating_component == _("Rent"):
|
||||
row.operating_cost = 250
|
||||
break
|
||||
|
||||
w1.save()
|
||||
# updating after setting new rates in workstations
|
||||
bom_doc.update_cost()
|
||||
@@ -102,8 +111,24 @@ def make_workstation(*args, **kwargs):
|
||||
workstation_name = args.workstation_name or args.workstation
|
||||
if not frappe.db.exists("Workstation", workstation_name):
|
||||
doc = frappe.get_doc({"doctype": "Workstation", "workstation_name": workstation_name})
|
||||
doc.hour_rate_rent = args.get("hour_rate_rent")
|
||||
doc.hour_rate_labour = args.get("hour_rate_labour")
|
||||
if args.get("hour_rate_rent"):
|
||||
doc.append(
|
||||
"workstation_costs",
|
||||
{
|
||||
"operating_component": _("Rent"),
|
||||
"operating_cost": args.get("hour_rate_rent"),
|
||||
},
|
||||
)
|
||||
|
||||
if args.get("hour_rate_labour"):
|
||||
doc.append(
|
||||
"workstation_costs",
|
||||
{
|
||||
"operating_component": _("Wages"),
|
||||
"operating_cost": args.get("hour_rate_labour"),
|
||||
},
|
||||
)
|
||||
|
||||
doc.workstation_type = args.get("workstation_type")
|
||||
doc.insert()
|
||||
|
||||
|
||||
@@ -27,11 +27,8 @@
|
||||
"column_break_etmc",
|
||||
"off_status_image",
|
||||
"over_heads",
|
||||
"hour_rate_electricity",
|
||||
"hour_rate_consumable",
|
||||
"column_break_11",
|
||||
"hour_rate_rent",
|
||||
"hour_rate_labour",
|
||||
"section_break_auzm",
|
||||
"workstation_costs",
|
||||
"section_break_11",
|
||||
"hour_rate",
|
||||
"workstaion_description",
|
||||
@@ -68,50 +65,6 @@
|
||||
"label": "Operating Costs",
|
||||
"oldfieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate_electricity",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Electricity Cost",
|
||||
"non_negative": 1,
|
||||
"oldfieldname": "hour_rate_electricity",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate_consumable",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Consumable Cost",
|
||||
"non_negative": 1,
|
||||
"oldfieldname": "hour_rate_consumable",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate_rent",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Rent Cost",
|
||||
"non_negative": 1,
|
||||
"oldfieldname": "hour_rate_rent",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"description": "Wages per hour",
|
||||
"fieldname": "hour_rate_labour",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Wages",
|
||||
"non_negative": 1,
|
||||
"oldfieldname": "hour_rate_labour",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate",
|
||||
@@ -252,6 +205,17 @@
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_auzm",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Operating Costs (Per Hour)"
|
||||
},
|
||||
{
|
||||
"fieldname": "workstation_costs",
|
||||
"fieldtype": "Table",
|
||||
"label": "Operating Components Cost",
|
||||
"options": "Workstation Cost"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
@@ -259,7 +223,7 @@
|
||||
"idx": 1,
|
||||
"image_field": "on_status_image",
|
||||
"links": [],
|
||||
"modified": "2025-07-13 16:02:13.615001",
|
||||
"modified": "2025-08-19 12:07:05.374386",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Workstation",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe import _, bold
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
@@ -44,6 +44,7 @@ class Workstation(Document):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.manufacturing.doctype.workstation_cost.workstation_cost import WorkstationCost
|
||||
from erpnext.manufacturing.doctype.workstation_working_hour.workstation_working_hour import (
|
||||
WorkstationWorkingHour,
|
||||
)
|
||||
@@ -52,10 +53,6 @@ class Workstation(Document):
|
||||
disabled: DF.Check
|
||||
holiday_list: DF.Link | None
|
||||
hour_rate: DF.Currency
|
||||
hour_rate_consumable: DF.Currency
|
||||
hour_rate_electricity: DF.Currency
|
||||
hour_rate_labour: DF.Currency
|
||||
hour_rate_rent: DF.Currency
|
||||
off_status_image: DF.AttachImage | None
|
||||
on_status_image: DF.AttachImage | None
|
||||
plant_floor: DF.Link | None
|
||||
@@ -64,10 +61,26 @@ class Workstation(Document):
|
||||
total_working_hours: DF.Float
|
||||
warehouse: DF.Link | None
|
||||
working_hours: DF.Table[WorkstationWorkingHour]
|
||||
workstation_costs: DF.Table[WorkstationCost]
|
||||
workstation_name: DF.Data
|
||||
workstation_type: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
self.validate_duplicate_operating_component()
|
||||
|
||||
def validate_duplicate_operating_component(self):
|
||||
components = []
|
||||
for row in self.workstation_costs:
|
||||
if row.operating_component not in components:
|
||||
components.append(row.operating_component)
|
||||
else:
|
||||
frappe.throw(
|
||||
_("Duplicate Operating Component {0} found in Operating Components").format(
|
||||
bold(row.operating_component)
|
||||
)
|
||||
)
|
||||
|
||||
def before_save(self):
|
||||
self.set_data_based_on_workstation_type()
|
||||
self.set_hour_rate()
|
||||
@@ -95,36 +108,33 @@ class Workstation(Document):
|
||||
frappe.throw(_("Row #{0}: Start Time must be before End Time").format(row.idx))
|
||||
|
||||
def set_hour_rate(self):
|
||||
self.hour_rate = (
|
||||
flt(self.hour_rate_labour)
|
||||
+ flt(self.hour_rate_electricity)
|
||||
+ flt(self.hour_rate_consumable)
|
||||
+ flt(self.hour_rate_rent)
|
||||
)
|
||||
self.hour_rate = 0.0
|
||||
for row in self.workstation_costs:
|
||||
if row.operating_cost:
|
||||
self.hour_rate += flt(row.operating_cost)
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_data_based_on_workstation_type(self):
|
||||
if self.workstation_costs:
|
||||
return
|
||||
|
||||
if self.workstation_type:
|
||||
fields = [
|
||||
"hour_rate_labour",
|
||||
"hour_rate_electricity",
|
||||
"hour_rate_consumable",
|
||||
"hour_rate_rent",
|
||||
"hour_rate",
|
||||
"description",
|
||||
]
|
||||
data = frappe.get_all(
|
||||
"Workstation Cost",
|
||||
fields=["operating_component", "operating_cost", "idx"],
|
||||
filters={"parent": self.workstation_type, "parenttype": "Workstation Type"},
|
||||
order_by="idx",
|
||||
)
|
||||
|
||||
data = frappe.get_cached_value("Workstation Type", self.workstation_type, fields, as_dict=True)
|
||||
|
||||
if not data:
|
||||
return
|
||||
|
||||
for field in fields:
|
||||
if self.get(field):
|
||||
continue
|
||||
|
||||
if value := data.get(field):
|
||||
self.set(field, value)
|
||||
for row in data:
|
||||
self.append(
|
||||
"workstation_costs",
|
||||
{
|
||||
"operating_component": row.operating_component,
|
||||
"operating_cost": row.operating_cost,
|
||||
"idx": row.idx,
|
||||
},
|
||||
)
|
||||
|
||||
def on_update(self):
|
||||
self.validate_overlap_for_operation_timings()
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestWorkstationCost(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for WorkstationCost.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Workstation Cost", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-08-17 16:43:13.542333",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"operating_component",
|
||||
"operating_cost"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "operating_cost",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Operating Cost",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "operating_component",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Operating Component",
|
||||
"options": "Workstation Operating Component",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-17 19:21:02.725365",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Workstation Cost",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class WorkstationCost(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
operating_component: DF.Link
|
||||
operating_cost: DF.Currency
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestWorkstationOperatingComponent(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for WorkstationOperatingComponent.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Workstation Operating Component", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:component_name",
|
||||
"creation": "2025-08-17 16:49:30.711201",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"component_name",
|
||||
"section_break_ewdg",
|
||||
"accounts"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "component_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Component Name",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_ewdg",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounts",
|
||||
"fieldtype": "Table",
|
||||
"label": "Component Expense Account",
|
||||
"options": "Workstation Operating Component Account"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-17 19:23:47.510540",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Workstation Operating Component",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Manufacturing User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Manufacturing Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class WorkstationOperatingComponent(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.manufacturing.doctype.workstation_operating_component_account.workstation_operating_component_account import (
|
||||
WorkstationOperatingComponentAccount,
|
||||
)
|
||||
|
||||
accounts: DF.Table[WorkstationOperatingComponentAccount]
|
||||
component_name: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestWorkstationOperatingComponentAccount(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for WorkstationOperatingComponentAccount.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Workstation Operating Component Account", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-08-17 19:21:36.356779",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company",
|
||||
"expense_account"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "expense_account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Expense Account",
|
||||
"options": "Account"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-17 19:24:01.487406",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Workstation Operating Component Account",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class WorkstationOperatingComponentAccount(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
company: DF.Link
|
||||
expense_account: DF.Link | None
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -10,12 +10,8 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"workstation_type",
|
||||
"over_heads",
|
||||
"hour_rate_electricity",
|
||||
"hour_rate_consumable",
|
||||
"column_break_5",
|
||||
"hour_rate_rent",
|
||||
"hour_rate_labour",
|
||||
"section_break_auzm",
|
||||
"workstation_costs",
|
||||
"section_break_8",
|
||||
"hour_rate",
|
||||
"description_tab",
|
||||
@@ -32,44 +28,6 @@
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "over_heads",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Operating Costs",
|
||||
"oldfieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate_electricity",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Electricity Cost",
|
||||
"oldfieldname": "hour_rate_electricity",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate_consumable",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Consumable Cost",
|
||||
"oldfieldname": "hour_rate_consumable",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate_rent",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Rent Cost",
|
||||
"oldfieldname": "hour_rate_rent",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"description": "Wages per hour",
|
||||
"fieldname": "hour_rate_labour",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Wages",
|
||||
"oldfieldname": "hour_rate_labour",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate",
|
||||
@@ -88,10 +46,6 @@
|
||||
"oldfieldtype": "Text",
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_5",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "description_tab",
|
||||
@@ -101,11 +55,22 @@
|
||||
{
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_auzm",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Operating Costs (Per Hour)"
|
||||
},
|
||||
{
|
||||
"fieldname": "workstation_costs",
|
||||
"fieldtype": "Table",
|
||||
"label": "Operating Components Cost",
|
||||
"options": "Workstation Cost"
|
||||
}
|
||||
],
|
||||
"icon": "icon-wrench",
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:11:00.946367",
|
||||
"modified": "2025-08-19 12:06:56.683558",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Workstation Type",
|
||||
@@ -125,9 +90,10 @@
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "ASC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _, bold
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt
|
||||
|
||||
@@ -15,25 +16,38 @@ class WorkstationType(Document):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.manufacturing.doctype.workstation_cost.workstation_cost import WorkstationCost
|
||||
|
||||
description: DF.SmallText | None
|
||||
hour_rate: DF.Currency
|
||||
hour_rate_consumable: DF.Currency
|
||||
hour_rate_electricity: DF.Currency
|
||||
hour_rate_labour: DF.Currency
|
||||
hour_rate_rent: DF.Currency
|
||||
workstation_costs: DF.Table[WorkstationCost]
|
||||
workstation_type: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
self.validate_duplicate_operating_component()
|
||||
|
||||
def validate_duplicate_operating_component(self):
|
||||
components = []
|
||||
for row in self.workstation_costs:
|
||||
if row.operating_component not in components:
|
||||
components.append(row.operating_component)
|
||||
else:
|
||||
frappe.throw(
|
||||
_("Duplicate Operating Component {0} found in Operating Components").format(
|
||||
bold(row.operating_component)
|
||||
)
|
||||
)
|
||||
|
||||
def before_save(self):
|
||||
self.set_hour_rate()
|
||||
|
||||
def set_hour_rate(self):
|
||||
self.hour_rate = (
|
||||
flt(self.hour_rate_labour)
|
||||
+ flt(self.hour_rate_electricity)
|
||||
+ flt(self.hour_rate_consumable)
|
||||
+ flt(self.hour_rate_rent)
|
||||
)
|
||||
self.hour_rate = 0.0
|
||||
|
||||
for row in self.workstation_costs:
|
||||
if row.operating_cost:
|
||||
self.hour_rate += flt(row.operating_cost)
|
||||
|
||||
|
||||
def get_workstations(workstation_type):
|
||||
|
||||
@@ -434,3 +434,5 @@ erpnext.patches.v15_0.update_uae_zero_rated_fetch
|
||||
erpnext.patches.v15_0.add_company_payment_gateway_account
|
||||
erpnext.patches.v16_0.update_serial_no_reference_name
|
||||
erpnext.patches.v16_0.set_invoice_type_in_pos_settings
|
||||
erpnext.patches.v15_0.update_fieldname_in_accounting_dimension_filter
|
||||
erpnext.patches.v16_0.make_workstation_operating_components #1
|
||||
|
||||
@@ -48,6 +48,7 @@ def execute():
|
||||
dunning.validate()
|
||||
|
||||
dunning.flags.ignore_validate_update_after_submit = True
|
||||
dunning.flags.ignore_links = True
|
||||
dunning.save()
|
||||
|
||||
# Reverse entries only if dunning is submitted and not resolved
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import frappe
|
||||
from frappe.query_builder import DocType
|
||||
|
||||
|
||||
def execute():
|
||||
default_accounting_dimension()
|
||||
ADF = DocType("Accounting Dimension Filter")
|
||||
AD = DocType("Accounting Dimension")
|
||||
|
||||
accounting_dimension_filter = (
|
||||
frappe.qb.from_(ADF)
|
||||
.join(AD)
|
||||
.on(AD.document_type == ADF.accounting_dimension)
|
||||
.select(ADF.name, AD.fieldname, ADF.accounting_dimension)
|
||||
).run(as_dict=True)
|
||||
|
||||
for doc in accounting_dimension_filter:
|
||||
value = doc.fieldname or frappe.scrub(doc.accounting_dimension)
|
||||
frappe.db.set_value(
|
||||
"Accounting Dimension Filter",
|
||||
doc.name,
|
||||
"fieldname",
|
||||
value,
|
||||
update_modified=False,
|
||||
)
|
||||
|
||||
|
||||
def default_accounting_dimension():
|
||||
ADF = DocType("Accounting Dimension Filter")
|
||||
for dim in ("Cost Center", "Project"):
|
||||
(
|
||||
frappe.qb.update(ADF)
|
||||
.set(ADF.fieldname, frappe.scrub(dim))
|
||||
.where(ADF.accounting_dimension == dim)
|
||||
.run()
|
||||
)
|
||||
@@ -0,0 +1,79 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
|
||||
def get_operating_cost_account(company):
|
||||
company_details = frappe.db.get_value(
|
||||
"Company", company, ["default_operating_cost_account", "default_expense_account"], as_dict=True
|
||||
)
|
||||
|
||||
return company_details.get("default_operating_cost_account") or company_details.get(
|
||||
"default_expense_account"
|
||||
)
|
||||
|
||||
|
||||
def execute():
|
||||
components = [
|
||||
"Electricity",
|
||||
"Consumables",
|
||||
"Rent",
|
||||
"Wages",
|
||||
]
|
||||
|
||||
companies = frappe.get_all("Company", filters={"is_group": 0}, pluck="name")
|
||||
|
||||
for component in components:
|
||||
component = _(component)
|
||||
if not frappe.db.exists("Workstation Operating Component", component):
|
||||
doc = frappe.new_doc("Workstation Operating Component")
|
||||
doc.component_name = component
|
||||
|
||||
for company in companies:
|
||||
operating_cost_account = get_operating_cost_account(company)
|
||||
|
||||
doc.append("accounts", {"company": company, "expense_account": operating_cost_account})
|
||||
|
||||
doc.insert()
|
||||
|
||||
workstations = frappe.get_all("Workstation", filters={"hour_rate": (">", 0.0)}, pluck="name") or []
|
||||
workstation_types = (
|
||||
frappe.get_all("Workstation Type", filters={"hour_rate": (">", 0.0)}, pluck="name") or []
|
||||
)
|
||||
|
||||
if not workstations and not workstation_types:
|
||||
return
|
||||
|
||||
components_map = {
|
||||
"hour_rate_electricity": _("Electricity"),
|
||||
"hour_rate_consumable": _("Consumables"),
|
||||
"hour_rate_rent": _("Rent"),
|
||||
"hour_rate_labour": _("Wages"),
|
||||
}
|
||||
|
||||
for workstation in workstations:
|
||||
doc = frappe.get_doc("Workstation", workstation)
|
||||
for field, component in components_map.items():
|
||||
if doc.get(field):
|
||||
doc.append(
|
||||
"workstation_costs",
|
||||
{
|
||||
"operating_component": component,
|
||||
"operating_cost": doc.get(field),
|
||||
},
|
||||
)
|
||||
|
||||
doc.save()
|
||||
|
||||
for workstation_type in workstation_types:
|
||||
doc = frappe.get_doc("Workstation Type", workstation_type)
|
||||
for field, component in components_map.items():
|
||||
if doc.get(field):
|
||||
doc.append(
|
||||
"workstation_costs",
|
||||
{
|
||||
"operating_component": component,
|
||||
"operating_cost": doc.get(field),
|
||||
},
|
||||
)
|
||||
|
||||
doc.save()
|
||||
@@ -329,12 +329,16 @@ def get_projectwise_timesheet_data(project=None, parent=None, from_time=None, to
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_timesheet_detail_rate(timelog, currency):
|
||||
timelog_detail = frappe.db.sql(
|
||||
f"""SELECT tsd.billing_amount as billing_amount,
|
||||
ts.currency as currency FROM `tabTimesheet Detail` tsd
|
||||
INNER JOIN `tabTimesheet` ts ON ts.name=tsd.parent
|
||||
WHERE tsd.name = '{timelog}'""",
|
||||
as_dict=1,
|
||||
ts = frappe.qb.DocType("Timesheet")
|
||||
ts_detail = frappe.qb.DocType("Timesheet Detail")
|
||||
|
||||
timelog_detail = (
|
||||
frappe.qb.from_(ts_detail)
|
||||
.inner_join(ts)
|
||||
.on(ts.name == ts_detail.parent)
|
||||
.select(ts_detail.billing_amount.as_("billing_amount"), ts.currency.as_("currency"))
|
||||
.where(ts_detail.name == timelog)
|
||||
.run(as_dict=1)
|
||||
)[0]
|
||||
|
||||
if timelog_detail.currency:
|
||||
|
||||
@@ -5,61 +5,69 @@
|
||||
frappe.provide("erpnext.taxes");
|
||||
|
||||
erpnext.accounts.taxes = {
|
||||
setup_tax_validations: function(doctype) {
|
||||
setup_tax_validations: function (doctype) {
|
||||
let me = this;
|
||||
frappe.ui.form.on(doctype, {
|
||||
setup: function(frm) {
|
||||
setup: function (frm) {
|
||||
// set conditional display for rate column in taxes
|
||||
$(frm.wrapper).on('grid-row-render', function(e, grid_row) {
|
||||
if(['Sales Taxes and Charges', 'Purchase Taxes and Charges'].includes(grid_row.doc.doctype)) {
|
||||
$(frm.wrapper).on("grid-row-render", function (e, grid_row) {
|
||||
if (
|
||||
["Sales Taxes and Charges", "Purchase Taxes and Charges"].includes(
|
||||
grid_row.doc.doctype
|
||||
)
|
||||
) {
|
||||
me.set_conditional_mandatory_rate_or_amount(grid_row);
|
||||
}
|
||||
});
|
||||
},
|
||||
onload: function(frm) {
|
||||
if(frm.get_field("taxes")) {
|
||||
frm.set_query("account_head", "taxes", function(doc) {
|
||||
if(frm.cscript.tax_table == "Sales Taxes and Charges") {
|
||||
onload: function (frm) {
|
||||
if (frm.get_field("taxes")) {
|
||||
frm.set_query("account_head", "taxes", function (doc) {
|
||||
if (frm.cscript.tax_table == "Sales Taxes and Charges") {
|
||||
var account_type = ["Tax", "Chargeable", "Expense Account"];
|
||||
} else {
|
||||
var account_type = ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation"];
|
||||
var account_type = [
|
||||
"Tax",
|
||||
"Chargeable",
|
||||
"Income Account",
|
||||
"Expenses Included In Valuation",
|
||||
];
|
||||
}
|
||||
|
||||
return {
|
||||
query: "erpnext.controllers.queries.tax_account_query",
|
||||
filters: {
|
||||
"account_type": account_type,
|
||||
"company": doc.company,
|
||||
}
|
||||
}
|
||||
account_type: account_type,
|
||||
company: doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
frm.set_query("cost_center", "taxes", function(doc) {
|
||||
frm.set_query("cost_center", "taxes", function (doc) {
|
||||
return {
|
||||
filters: {
|
||||
"company": doc.company,
|
||||
"is_group": 0
|
||||
}
|
||||
company: doc.company,
|
||||
is_group: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
validate: function(frm) {
|
||||
validate: function (frm) {
|
||||
// neither is absolutely mandatory
|
||||
if(frm.get_docfield("taxes")) {
|
||||
if (frm.get_docfield("taxes")) {
|
||||
frm.get_docfield("taxes", "rate").reqd = 0;
|
||||
frm.get_docfield("taxes", "tax_amount").reqd = 0;
|
||||
}
|
||||
|
||||
},
|
||||
taxes_on_form_rendered: function(frm) {
|
||||
taxes_on_form_rendered: function (frm) {
|
||||
me.set_conditional_mandatory_rate_or_amount(frm.open_grid_row());
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
set_conditional_mandatory_rate_or_amount: function(grid_row) {
|
||||
if(grid_row) {
|
||||
if(grid_row.doc.charge_type==="Actual") {
|
||||
set_conditional_mandatory_rate_or_amount: function (grid_row) {
|
||||
if (grid_row) {
|
||||
if (grid_row.doc.charge_type === "Actual") {
|
||||
grid_row.toggle_editable("tax_amount", true);
|
||||
grid_row.toggle_reqd("tax_amount", true);
|
||||
grid_row.toggle_editable("rate", false);
|
||||
@@ -73,31 +81,45 @@ erpnext.accounts.taxes = {
|
||||
}
|
||||
},
|
||||
|
||||
validate_taxes_and_charges: function(cdt, cdn) {
|
||||
validate_taxes_and_charges: function (cdt, cdn) {
|
||||
let d = locals[cdt][cdn];
|
||||
let msg = "";
|
||||
|
||||
if (d.account_head && !d.description) {
|
||||
// set description from account head
|
||||
d.description = d.account_head.split(' - ').slice(0, -1).join(' - ');
|
||||
d.description = d.account_head.split(" - ").slice(0, -1).join(" - ");
|
||||
}
|
||||
|
||||
if (!d.charge_type && (d.row_id || d.rate || d.tax_amount)) {
|
||||
msg = __("Please select Charge Type first");
|
||||
d.row_id = "";
|
||||
d.rate = d.tax_amount = 0.0;
|
||||
} else if ((d.charge_type == 'Actual' || d.charge_type == 'On Net Total' || d.charge_type == 'On Paid Amount') && d.row_id) {
|
||||
msg = __("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'");
|
||||
} else if (
|
||||
(d.charge_type == "Actual" ||
|
||||
d.charge_type == "On Net Total" ||
|
||||
d.charge_type == "On Paid Amount") &&
|
||||
d.row_id
|
||||
) {
|
||||
msg = __(
|
||||
"Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'"
|
||||
);
|
||||
d.row_id = "";
|
||||
} else if ((d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total') && d.row_id) {
|
||||
} else if (
|
||||
(d.charge_type == "On Previous Row Amount" || d.charge_type == "On Previous Row Total") &&
|
||||
d.row_id
|
||||
) {
|
||||
if (d.idx == 1) {
|
||||
msg = __("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row");
|
||||
d.charge_type = '';
|
||||
msg = __(
|
||||
"Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"
|
||||
);
|
||||
d.charge_type = "";
|
||||
} else if (!d.row_id) {
|
||||
msg = __("Please specify a valid Row ID for row {0} in table {1}", [d.idx, __(d.doctype)]);
|
||||
d.row_id = "";
|
||||
} else if (d.row_id && d.row_id >= d.idx) {
|
||||
msg = __("Cannot refer row number greater than or equal to current row number for this Charge type");
|
||||
msg = __(
|
||||
"Cannot refer row number greater than or equal to current row number for this Charge type"
|
||||
);
|
||||
d.row_id = "";
|
||||
}
|
||||
}
|
||||
@@ -106,13 +128,12 @@ erpnext.accounts.taxes = {
|
||||
refresh_field("taxes");
|
||||
frappe.throw(msg);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
setup_tax_filters: function(doctype) {
|
||||
setup_tax_filters: function (doctype) {
|
||||
let me = this;
|
||||
frappe.ui.form.on(doctype, {
|
||||
account_head: function(frm, cdt, cdn) {
|
||||
account_head: function (frm, cdt, cdn) {
|
||||
let d = locals[cdt][cdn];
|
||||
|
||||
if (d.docstatus == 1) {
|
||||
@@ -120,150 +141,157 @@ erpnext.accounts.taxes = {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!d.charge_type && d.account_head){
|
||||
if (!d.charge_type && d.account_head) {
|
||||
frappe.msgprint(__("Please select Charge Type first"));
|
||||
frappe.model.set_value(cdt, cdn, "account_head", "");
|
||||
} else if (d.account_head) {
|
||||
frappe.call({
|
||||
type:"GET",
|
||||
type: "GET",
|
||||
method: "erpnext.controllers.accounts_controller.get_tax_rate",
|
||||
args: {"account_head":d.account_head},
|
||||
callback: function(r) {
|
||||
if (d.charge_type!=="Actual") {
|
||||
args: { account_head: d.account_head },
|
||||
callback: function (r) {
|
||||
if (d.charge_type !== "Actual") {
|
||||
frappe.model.set_value(cdt, cdn, "rate", r.message.tax_rate || 0);
|
||||
}
|
||||
frappe.model.set_value(cdt, cdn, "description", r.message.account_name);
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
row_id: function(frm, cdt, cdn) {
|
||||
row_id: function (frm, cdt, cdn) {
|
||||
me.validate_taxes_and_charges(cdt, cdn);
|
||||
},
|
||||
rate: function(frm, cdt, cdn) {
|
||||
rate: function (frm, cdt, cdn) {
|
||||
me.validate_taxes_and_charges(cdt, cdn);
|
||||
},
|
||||
tax_amount: function(frm, cdt, cdn) {
|
||||
tax_amount: function (frm, cdt, cdn) {
|
||||
me.validate_taxes_and_charges(cdt, cdn);
|
||||
},
|
||||
charge_type: function(frm, cdt, cdn) {
|
||||
charge_type: function (frm, cdt, cdn) {
|
||||
me.validate_taxes_and_charges(cdt, cdn);
|
||||
let open_form = frm.open_grid_row();
|
||||
if(open_form) {
|
||||
if (open_form) {
|
||||
me.set_conditional_mandatory_rate_or_amount(open_form);
|
||||
} else {
|
||||
// apply in current row
|
||||
me.set_conditional_mandatory_rate_or_amount(frm.get_field('taxes').grid.get_row(cdn));
|
||||
me.set_conditional_mandatory_rate_or_amount(frm.get_field("taxes").grid.get_row(cdn));
|
||||
}
|
||||
},
|
||||
included_in_print_rate: function(frm, cdt, cdn) {
|
||||
included_in_print_rate: function (frm, cdt, cdn) {
|
||||
let tax = frappe.get_doc(cdt, cdn);
|
||||
try {
|
||||
me.validate_taxes_and_charges(cdt, cdn);
|
||||
me.validate_inclusive_tax(tax, frm);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
tax.included_in_print_rate = 0;
|
||||
refresh_field("included_in_print_rate", tax.name, tax.parentfield);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
validate_inclusive_tax: function(tax, frm) {
|
||||
validate_inclusive_tax: function (tax, frm) {
|
||||
this.frm = this.frm || frm;
|
||||
let actual_type_error = function() {
|
||||
var msg = __("Actual type tax cannot be included in Item rate in row {0}", [tax.idx])
|
||||
let actual_type_error = function () {
|
||||
var msg = __("Actual type tax cannot be included in Item rate in row {0}", [tax.idx]);
|
||||
frappe.throw(msg);
|
||||
};
|
||||
|
||||
let on_previous_row_error = function(row_range) {
|
||||
var msg = __("For row {0} in {1}. To include {2} in Item rate, rows {3} must also be included",
|
||||
[tax.idx, __(tax.doctype), tax.charge_type, row_range])
|
||||
let on_previous_row_error = function (row_range) {
|
||||
var msg = __("For row {0} in {1}. To include {2} in Item rate, rows {3} must also be included", [
|
||||
tax.idx,
|
||||
__(tax.doctype),
|
||||
tax.charge_type,
|
||||
row_range,
|
||||
]);
|
||||
frappe.throw(msg);
|
||||
};
|
||||
|
||||
if(cint(tax.included_in_print_rate)) {
|
||||
if(tax.charge_type == "Actual") {
|
||||
if (cint(tax.included_in_print_rate)) {
|
||||
if (tax.charge_type == "Actual") {
|
||||
// inclusive tax cannot be of type Actual
|
||||
actual_type_error();
|
||||
} else if (tax.charge_type == "On Previous Row Amount" && this.frm &&
|
||||
} else if (
|
||||
tax.charge_type == "On Previous Row Amount" &&
|
||||
this.frm &&
|
||||
!cint(this.frm.doc["taxes"][tax.row_id - 1].included_in_print_rate)
|
||||
) {
|
||||
// referred row should also be an inclusive tax
|
||||
on_previous_row_error(tax.row_id);
|
||||
} else if (tax.charge_type == "On Previous Row Total" && this.frm) {
|
||||
var taxes_not_included = $.map(this.frm.doc["taxes"].slice(0, tax.row_id),
|
||||
function(t) { return cint(t.included_in_print_rate) ? null : t; });
|
||||
if(taxes_not_included.length > 0) {
|
||||
var taxes_not_included = $.map(this.frm.doc["taxes"].slice(0, tax.row_id), function (t) {
|
||||
return cint(t.included_in_print_rate) ? null : t;
|
||||
});
|
||||
if (taxes_not_included.length > 0) {
|
||||
// all rows above this tax should be inclusive
|
||||
on_previous_row_error(tax.row_id == 1 ? "1" : "1 - " + tax.row_id);
|
||||
}
|
||||
} else if(tax.category == "Valuation") {
|
||||
} else if (tax.category == "Valuation") {
|
||||
frappe.throw(__("Valuation type charges can not marked as Inclusive"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
erpnext.accounts.payment_triggers = {
|
||||
setup: function(doctype) {
|
||||
setup: function (doctype) {
|
||||
frappe.ui.form.on(doctype, {
|
||||
allocate_advances_automatically(frm) {
|
||||
frm.trigger('fetch_advances');
|
||||
frm.trigger("fetch_advances");
|
||||
},
|
||||
|
||||
only_include_allocated_payments(frm) {
|
||||
frm.trigger('fetch_advances');
|
||||
frm.trigger("fetch_advances");
|
||||
},
|
||||
|
||||
fetch_advances(frm) {
|
||||
if(frm.doc.allocate_advances_automatically) {
|
||||
if (frm.doc.allocate_advances_automatically) {
|
||||
frappe.call({
|
||||
doc: frm.doc,
|
||||
method: "set_advances",
|
||||
callback: function(r, rt) {
|
||||
callback: function (r, rt) {
|
||||
refresh_field("advances");
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
erpnext.accounts.pos = {
|
||||
setup: function(doctype) {
|
||||
setup: function (doctype) {
|
||||
frappe.ui.form.on(doctype, {
|
||||
mode_of_payment: function(frm, cdt, cdn) {
|
||||
mode_of_payment: function (frm, cdt, cdn) {
|
||||
var d = locals[cdt][cdn];
|
||||
get_payment_mode_account(frm, d.mode_of_payment, function(account){
|
||||
frappe.model.set_value(cdt, cdn, 'account', account)
|
||||
})
|
||||
}
|
||||
get_payment_mode_account(frm, d.mode_of_payment, function (account) {
|
||||
frappe.model.set_value(cdt, cdn, "account", account);
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
get_payment_mode_account: function(frm, mode_of_payment, callback) {
|
||||
if(!frm.doc.company) {
|
||||
frappe.throw({message:__("Please select a Company first."), title: __("Mandatory")});
|
||||
get_payment_mode_account: function (frm, mode_of_payment, callback) {
|
||||
if (!frm.doc.company) {
|
||||
frappe.throw({ message: __("Please select a Company first."), title: __("Mandatory") });
|
||||
}
|
||||
|
||||
if(!mode_of_payment) {
|
||||
if (!mode_of_payment) {
|
||||
return;
|
||||
}
|
||||
|
||||
return frappe.call({
|
||||
return frappe.call({
|
||||
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account",
|
||||
args: {
|
||||
"mode_of_payment": mode_of_payment,
|
||||
"company": frm.doc.company
|
||||
mode_of_payment: mode_of_payment,
|
||||
company: frm.doc.company,
|
||||
},
|
||||
callback: function(r, rt) {
|
||||
if(r.message) {
|
||||
callback(r.message.account)
|
||||
callback: function (r, rt) {
|
||||
if (r.message) {
|
||||
callback(r.message.account);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ frappe.provide("erpnext.buying");
|
||||
// cur_frm.add_fetch('project', 'cost_center', 'cost_center');
|
||||
|
||||
erpnext.buying = {
|
||||
setup_buying_controller: function() {
|
||||
setup_buying_controller: function () {
|
||||
erpnext.buying.BuyingController = class BuyingController extends erpnext.TransactionController {
|
||||
setup() {
|
||||
super.setup();
|
||||
@@ -17,11 +17,11 @@ erpnext.buying = {
|
||||
this.setup_queries(doc, cdt, cdn);
|
||||
super.onload();
|
||||
|
||||
this.frm.set_query('shipping_rule', function() {
|
||||
this.frm.set_query("shipping_rule", function () {
|
||||
return {
|
||||
filters: {
|
||||
"shipping_rule_type": "Buying"
|
||||
}
|
||||
shipping_rule_type: "Buying",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -33,29 +33,28 @@ erpnext.buying = {
|
||||
};
|
||||
});
|
||||
|
||||
if (this.frm.doc.__islocal
|
||||
&& frappe.meta.has_field(this.frm.doc.doctype, "disable_rounded_total")) {
|
||||
|
||||
var df = frappe.meta.get_docfield(this.frm.doc.doctype, "disable_rounded_total");
|
||||
var disable = cint(df.default) || cint(frappe.sys_defaults.disable_rounded_total);
|
||||
this.frm.set_value("disable_rounded_total", disable);
|
||||
if (
|
||||
this.frm.doc.__islocal &&
|
||||
frappe.meta.has_field(this.frm.doc.doctype, "disable_rounded_total")
|
||||
) {
|
||||
var df = frappe.meta.get_docfield(this.frm.doc.doctype, "disable_rounded_total");
|
||||
var disable = cint(df.default) || cint(frappe.sys_defaults.disable_rounded_total);
|
||||
this.frm.set_value("disable_rounded_total", disable);
|
||||
}
|
||||
|
||||
|
||||
// no idea where me is coming from
|
||||
if(this.frm.get_field('shipping_address')) {
|
||||
if (this.frm.get_field("shipping_address")) {
|
||||
this.frm.set_query("shipping_address", () => {
|
||||
if(this.frm.doc.customer) {
|
||||
if (this.frm.doc.customer) {
|
||||
return {
|
||||
query: 'frappe.contacts.doctype.address.address.address_query',
|
||||
filters: { link_doctype: 'Customer', link_name: this.frm.doc.customer }
|
||||
query: "frappe.contacts.doctype.address.address.address_query",
|
||||
filters: { link_doctype: "Customer", link_name: this.frm.doc.customer },
|
||||
};
|
||||
} else
|
||||
return erpnext.queries.company_address_query(this.frm.doc)
|
||||
} else return erpnext.queries.company_address_query(this.frm.doc);
|
||||
});
|
||||
}
|
||||
|
||||
if(this.frm.get_field('dispatch_address')) {
|
||||
if (this.frm.get_field("dispatch_address")) {
|
||||
this.frm.set_query("dispatch_address", () => {
|
||||
return erpnext.queries.address_query(this.frm.doc);
|
||||
});
|
||||
@@ -65,76 +64,77 @@ erpnext.buying = {
|
||||
setup_queries(doc, cdt, cdn) {
|
||||
var me = this;
|
||||
|
||||
if(this.frm.fields_dict.buying_price_list) {
|
||||
this.frm.set_query("buying_price_list", function() {
|
||||
return{
|
||||
filters: { 'buying': 1 }
|
||||
}
|
||||
if (this.frm.fields_dict.buying_price_list) {
|
||||
this.frm.set_query("buying_price_list", function () {
|
||||
return {
|
||||
filters: { buying: 1 },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if(this.frm.fields_dict.tc_name) {
|
||||
this.frm.set_query("tc_name", function() {
|
||||
return{
|
||||
filters: { 'buying': 1 }
|
||||
}
|
||||
if (this.frm.fields_dict.tc_name) {
|
||||
this.frm.set_query("tc_name", function () {
|
||||
return {
|
||||
filters: { buying: 1 },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
me.frm.set_query('supplier', erpnext.queries.supplier);
|
||||
me.frm.set_query('contact_person', erpnext.queries.contact_query);
|
||||
me.frm.set_query('supplier_address', erpnext.queries.address_query);
|
||||
me.frm.set_query("supplier", erpnext.queries.supplier);
|
||||
me.frm.set_query("contact_person", erpnext.queries.contact_query);
|
||||
me.frm.set_query("supplier_address", erpnext.queries.address_query);
|
||||
|
||||
me.frm.set_query('billing_address', erpnext.queries.company_address_query);
|
||||
me.frm.set_query("billing_address", erpnext.queries.company_address_query);
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype);
|
||||
|
||||
this.frm.set_query("item_code", "items", function() {
|
||||
this.frm.set_query("item_code", "items", function () {
|
||||
if (me.frm.doc.is_subcontracted) {
|
||||
var filters = {'supplier': me.frm.doc.supplier};
|
||||
var filters = { supplier: me.frm.doc.supplier };
|
||||
if (me.frm.doc.is_old_subcontracting_flow) {
|
||||
filters["is_sub_contracted_item"] = 1;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
filters["is_stock_item"] = 0;
|
||||
}
|
||||
|
||||
return{
|
||||
return {
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
filters: filters
|
||||
}
|
||||
}
|
||||
else {
|
||||
return{
|
||||
filters: filters,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
filters: { 'supplier': me.frm.doc.supplier, 'is_purchase_item': 1, 'has_variants': 0}
|
||||
}
|
||||
filters: { supplier: me.frm.doc.supplier, is_purchase_item: 1, has_variants: 0 },
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.frm.set_query("manufacturer", "items", function(doc, cdt, cdn) {
|
||||
this.frm.set_query("manufacturer", "items", function (doc, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
return {
|
||||
query: "erpnext.controllers.queries.item_manufacturer_query",
|
||||
filters:{ 'item_code': row.item_code }
|
||||
}
|
||||
filters: { item_code: row.item_code },
|
||||
};
|
||||
});
|
||||
|
||||
if(this.frm.fields_dict["items"].grid.get_field('item_code')) {
|
||||
this.frm.set_query("item_tax_template", "items", function(doc, cdt, cdn) {
|
||||
return me.set_query_for_item_tax_template(doc, cdt, cdn)
|
||||
if (this.frm.fields_dict["items"].grid.get_field("item_code")) {
|
||||
this.frm.set_query("item_tax_template", "items", function (doc, cdt, cdn) {
|
||||
return me.set_query_for_item_tax_template(doc, cdt, cdn);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
refresh(doc) {
|
||||
frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'supplier', doctype: 'Supplier'};
|
||||
frappe.dynamic_link = { doc: this.frm.doc, fieldname: "supplier", doctype: "Supplier" };
|
||||
|
||||
this.frm.toggle_display("supplier_name",
|
||||
(this.frm.doc.supplier_name && this.frm.doc.supplier_name!==this.frm.doc.supplier));
|
||||
this.frm.toggle_display(
|
||||
"supplier_name",
|
||||
this.frm.doc.supplier_name && this.frm.doc.supplier_name !== this.frm.doc.supplier
|
||||
);
|
||||
|
||||
if(this.frm.doc.docstatus==0 &&
|
||||
(this.frm.doctype==="Purchase Order" || this.frm.doctype==="Material Request")) {
|
||||
if (
|
||||
this.frm.doc.docstatus == 0 &&
|
||||
(this.frm.doctype === "Purchase Order" || this.frm.doctype === "Material Request")
|
||||
) {
|
||||
this.set_from_product_bundle();
|
||||
}
|
||||
|
||||
@@ -143,45 +143,53 @@ erpnext.buying = {
|
||||
}
|
||||
|
||||
toggle_subcontracting_fields() {
|
||||
if (['Purchase Receipt', 'Purchase Invoice'].includes(this.frm.doc.doctype)) {
|
||||
this.frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty',
|
||||
'read_only', this.frm.doc.__onload && this.frm.doc.__onload.backflush_based_on === 'BOM');
|
||||
if (["Purchase Receipt", "Purchase Invoice"].includes(this.frm.doc.doctype)) {
|
||||
this.frm.fields_dict.supplied_items.grid.update_docfield_property(
|
||||
"consumed_qty",
|
||||
"read_only",
|
||||
this.frm.doc.__onload && this.frm.doc.__onload.backflush_based_on === "BOM"
|
||||
);
|
||||
|
||||
this.frm.set_df_property('supplied_items', 'cannot_add_rows', 1);
|
||||
this.frm.set_df_property('supplied_items', 'cannot_delete_rows', 1);
|
||||
this.frm.set_df_property("supplied_items", "cannot_add_rows", 1);
|
||||
this.frm.set_df_property("supplied_items", "cannot_delete_rows", 1);
|
||||
}
|
||||
}
|
||||
|
||||
supplier() {
|
||||
var me = this;
|
||||
erpnext.utils.get_party_details(this.frm, null, null, function(){
|
||||
erpnext.utils.get_party_details(this.frm, null, null, function () {
|
||||
me.apply_price_list();
|
||||
});
|
||||
}
|
||||
|
||||
company(){
|
||||
if(!frappe.meta.has_field(this.frm.doc.doctype, "billing_address")) return;
|
||||
company() {
|
||||
if (!frappe.meta.has_field(this.frm.doc.doctype, "billing_address")) return;
|
||||
|
||||
frappe.call({
|
||||
method: "erpnext.setup.doctype.company.company.get_billing_shipping_address",
|
||||
args: {
|
||||
name: this.frm.doc.company,
|
||||
billing_address:this.frm.doc.billing_address,
|
||||
shipping_address: this.frm.doc.shipping_address
|
||||
billing_address: this.frm.doc.billing_address,
|
||||
shipping_address: this.frm.doc.shipping_address,
|
||||
},
|
||||
callback: (r) => {
|
||||
this.frm.set_value("billing_address", r.message.primary_address || "");
|
||||
|
||||
if(!frappe.meta.has_field(this.frm.doc.doctype, "shipping_address")) return;
|
||||
if (!frappe.meta.has_field(this.frm.doc.doctype, "shipping_address")) return;
|
||||
this.frm.set_value("shipping_address", r.message.shipping_address || "");
|
||||
},
|
||||
});
|
||||
erpnext.utils.set_letter_head(this.frm)
|
||||
erpnext.utils.set_letter_head(this.frm);
|
||||
}
|
||||
|
||||
supplier_address() {
|
||||
erpnext.utils.get_address_display(this.frm);
|
||||
erpnext.utils.set_taxes_from_address(this.frm, "supplier_address", "supplier_address", "supplier_address");
|
||||
erpnext.utils.set_taxes_from_address(
|
||||
this.frm,
|
||||
"supplier_address",
|
||||
"supplier_address",
|
||||
"supplier_address"
|
||||
);
|
||||
}
|
||||
|
||||
buying_price_list() {
|
||||
@@ -201,24 +209,33 @@ erpnext.buying = {
|
||||
}
|
||||
|
||||
qty(doc, cdt, cdn) {
|
||||
if ((doc.doctype == "Purchase Receipt") || (doc.doctype == "Purchase Invoice" && doc.update_stock)) {
|
||||
this.calculate_received_qty(doc, cdt, cdn)
|
||||
if (
|
||||
doc.doctype == "Purchase Receipt" ||
|
||||
(doc.doctype == "Purchase Invoice" && doc.update_stock)
|
||||
) {
|
||||
this.calculate_received_qty(doc, cdt, cdn);
|
||||
}
|
||||
super.qty(doc, cdt, cdn);
|
||||
}
|
||||
|
||||
rejected_qty(doc, cdt, cdn) {
|
||||
this.calculate_received_qty(doc, cdt, cdn)
|
||||
this.calculate_received_qty(doc, cdt, cdn);
|
||||
}
|
||||
|
||||
calculate_received_qty(doc, cdt, cdn){
|
||||
calculate_received_qty(doc, cdt, cdn) {
|
||||
var item = frappe.get_doc(cdt, cdn);
|
||||
frappe.model.round_floats_in(item, ["qty", "rejected_qty"]);
|
||||
|
||||
if(!doc.is_return && this.validate_negative_quantity(cdt, cdn, item, ["qty", "rejected_qty"])){ return }
|
||||
if (
|
||||
!doc.is_return &&
|
||||
this.validate_negative_quantity(cdt, cdn, item, ["qty", "rejected_qty"])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let received_qty = flt(item.qty + item.rejected_qty, precision("received_qty", item));
|
||||
let received_stock_qty = flt(item.conversion_factor, precision("conversion_factor", item)) * flt(received_qty);
|
||||
let received_stock_qty =
|
||||
flt(item.conversion_factor, precision("conversion_factor", item)) * flt(received_qty);
|
||||
|
||||
frappe.model.set_value(cdt, cdn, "received_qty", received_qty);
|
||||
frappe.model.set_value(cdt, cdn, "received_stock_qty", received_stock_qty);
|
||||
@@ -228,24 +245,32 @@ erpnext.buying = {
|
||||
super.batch_no(doc, cdt, cdn);
|
||||
}
|
||||
|
||||
validate_negative_quantity(cdt, cdn, item, fieldnames){
|
||||
if(!item || !fieldnames) { return }
|
||||
validate_negative_quantity(cdt, cdn, item, fieldnames) {
|
||||
if (!item || !fieldnames) {
|
||||
return;
|
||||
}
|
||||
|
||||
var is_negative_qty = false;
|
||||
for(var i = 0; i<fieldnames.length; i++) {
|
||||
if(item[fieldnames[i]] < 0){
|
||||
frappe.msgprint(__("Row #{0}: {1} can not be negative for item {2}", [item.idx,__(frappe.meta.get_label(cdt, fieldnames[i], cdn)), item.item_code]));
|
||||
for (var i = 0; i < fieldnames.length; i++) {
|
||||
if (item[fieldnames[i]] < 0) {
|
||||
frappe.msgprint(
|
||||
__("Row #{0}: {1} can not be negative for item {2}", [
|
||||
item.idx,
|
||||
__(frappe.meta.get_label(cdt, fieldnames[i], cdn)),
|
||||
item.item_code,
|
||||
])
|
||||
);
|
||||
is_negative_qty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return is_negative_qty
|
||||
return is_negative_qty;
|
||||
}
|
||||
|
||||
warehouse(doc, cdt, cdn) {
|
||||
var item = frappe.get_doc(cdt, cdn);
|
||||
if(item.item_code && item.warehouse) {
|
||||
if (item.item_code && item.warehouse) {
|
||||
return this.frm.call({
|
||||
method: "erpnext.stock.get_item_details.get_bin_details",
|
||||
child: item,
|
||||
@@ -253,22 +278,21 @@ erpnext.buying = {
|
||||
item_code: item.item_code,
|
||||
warehouse: item.warehouse,
|
||||
company: doc.company,
|
||||
include_child_warehouses: true
|
||||
}
|
||||
include_child_warehouses: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
project(doc, cdt, cdn) {
|
||||
var item = frappe.get_doc(cdt, cdn);
|
||||
if(item.project) {
|
||||
$.each(this.frm.doc["items"] || [],
|
||||
function(i, other_item) {
|
||||
if(!other_item.project) {
|
||||
other_item.project = item.project;
|
||||
refresh_field("project", other_item.name, other_item.parentfield);
|
||||
}
|
||||
});
|
||||
if (item.project) {
|
||||
$.each(this.frm.doc["items"] || [], function (i, other_item) {
|
||||
if (!other_item.project) {
|
||||
other_item.project = item.project;
|
||||
refresh_field("project", other_item.name, other_item.parentfield);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +305,7 @@ erpnext.buying = {
|
||||
|
||||
category(doc, cdt, cdn) {
|
||||
// should be the category field of tax table
|
||||
if(cdt != doc.doctype) {
|
||||
if (cdt != doc.doctype) {
|
||||
this.calculate_taxes_and_totals();
|
||||
}
|
||||
}
|
||||
@@ -291,26 +315,42 @@ erpnext.buying = {
|
||||
|
||||
set_from_product_bundle() {
|
||||
var me = this;
|
||||
this.frm.add_custom_button(__("Product Bundle"), function() {
|
||||
erpnext.buying.get_items_from_product_bundle(me.frm);
|
||||
}, __("Get Items From"));
|
||||
this.frm.add_custom_button(
|
||||
__("Product Bundle"),
|
||||
function () {
|
||||
erpnext.buying.get_items_from_product_bundle(me.frm);
|
||||
},
|
||||
__("Get Items From")
|
||||
);
|
||||
}
|
||||
|
||||
shipping_address(){
|
||||
shipping_address() {
|
||||
var me = this;
|
||||
erpnext.utils.get_address_display(this.frm, "shipping_address",
|
||||
"shipping_address_display", true);
|
||||
erpnext.utils.get_address_display(
|
||||
this.frm,
|
||||
"shipping_address",
|
||||
"shipping_address_display",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
dispatch_address(){
|
||||
dispatch_address() {
|
||||
var me = this;
|
||||
erpnext.utils.get_address_display(this.frm, "dispatch_address",
|
||||
"dispatch_address_display", true);
|
||||
erpnext.utils.get_address_display(
|
||||
this.frm,
|
||||
"dispatch_address",
|
||||
"dispatch_address_display",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
billing_address() {
|
||||
erpnext.utils.get_address_display(this.frm, "billing_address",
|
||||
"billing_address_display", true);
|
||||
erpnext.utils.get_address_display(
|
||||
this.frm,
|
||||
"billing_address",
|
||||
"billing_address_display",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
tc_name() {
|
||||
@@ -320,37 +360,43 @@ erpnext.buying = {
|
||||
update_auto_repeat_reference(doc) {
|
||||
if (doc.auto_repeat) {
|
||||
frappe.call({
|
||||
method:"frappe.automation.doctype.auto_repeat.auto_repeat.update_reference",
|
||||
args:{
|
||||
method: "frappe.automation.doctype.auto_repeat.auto_repeat.update_reference",
|
||||
args: {
|
||||
docname: doc.auto_repeat,
|
||||
reference:doc.name
|
||||
reference: doc.name,
|
||||
},
|
||||
callback: function(r){
|
||||
if (r.message=="success") {
|
||||
frappe.show_alert({message:__("Auto repeat document updated"), indicator:'green'});
|
||||
callback: function (r) {
|
||||
if (r.message == "success") {
|
||||
frappe.show_alert({
|
||||
message: __("Auto repeat document updated"),
|
||||
indicator: "green",
|
||||
});
|
||||
} else {
|
||||
frappe.show_alert({message:__("An error occurred during the update process"), indicator:'red'});
|
||||
frappe.show_alert({
|
||||
message: __("An error occurred during the update process"),
|
||||
indicator: "red",
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
manufacturer(doc, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
|
||||
if(row.manufacturer) {
|
||||
if (row.manufacturer) {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.item_manufacturer.item_manufacturer.get_item_manufacturer_part_no",
|
||||
args: {
|
||||
'item_code': row.item_code,
|
||||
'manufacturer': row.manufacturer
|
||||
item_code: row.item_code,
|
||||
manufacturer: row.manufacturer,
|
||||
},
|
||||
callback: function(r) {
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
frappe.model.set_value(cdt, cdn, 'manufacturer_part_no', r.message);
|
||||
frappe.model.set_value(cdt, cdn, "manufacturer_part_no", r.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -359,19 +405,22 @@ erpnext.buying = {
|
||||
const row = locals[cdt][cdn];
|
||||
|
||||
if (row.manufacturer_part_no) {
|
||||
frappe.model.get_value('Item Manufacturer',
|
||||
frappe.model.get_value(
|
||||
"Item Manufacturer",
|
||||
{
|
||||
'item_code': row.item_code,
|
||||
'manufacturer': row.manufacturer,
|
||||
'manufacturer_part_no': row.manufacturer_part_no
|
||||
item_code: row.item_code,
|
||||
manufacturer: row.manufacturer,
|
||||
manufacturer_part_no: row.manufacturer_part_no,
|
||||
},
|
||||
'name',
|
||||
function(data) {
|
||||
"name",
|
||||
function (data) {
|
||||
if (!data) {
|
||||
let msg = {
|
||||
message: __("Manufacturer Part Number <b>{0}</b> is invalid", [row.manufacturer_part_no]),
|
||||
title: __("Invalid Part Number")
|
||||
}
|
||||
message: __("Manufacturer Part Number <b>{0}</b> is invalid", [
|
||||
row.manufacturer_part_no,
|
||||
]),
|
||||
title: __("Invalid Part Number"),
|
||||
};
|
||||
frappe.throw(msg);
|
||||
}
|
||||
}
|
||||
@@ -384,40 +433,42 @@ erpnext.buying = {
|
||||
let me = this;
|
||||
let fields = ["has_batch_no", "has_serial_no"];
|
||||
|
||||
frappe.db.get_value("Item", item.item_code, fields)
|
||||
.then((r) => {
|
||||
if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) {
|
||||
fields.forEach((field) => {
|
||||
item[field] = r.message[field];
|
||||
});
|
||||
frappe.db.get_value("Item", item.item_code, fields).then((r) => {
|
||||
if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) {
|
||||
fields.forEach((field) => {
|
||||
item[field] = r.message[field];
|
||||
});
|
||||
|
||||
item.type_of_transaction = item.qty > 0 ? "Inward" : "Outward";
|
||||
item.is_rejected = false;
|
||||
item.type_of_transaction = item.qty > 0 ? "Inward" : "Outward";
|
||||
item.is_rejected = false;
|
||||
|
||||
new erpnext.SerialBatchPackageSelector(
|
||||
me.frm, item, (r) => {
|
||||
if (r) {
|
||||
let qty = Math.abs(r.total_qty);
|
||||
if (doc.is_return) {
|
||||
qty = qty * -1;
|
||||
}
|
||||
|
||||
let update_values = {
|
||||
"serial_and_batch_bundle": r.name,
|
||||
"use_serial_batch_fields": 0,
|
||||
"qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item))
|
||||
}
|
||||
|
||||
if (r.warehouse) {
|
||||
update_values["warehouse"] = r.warehouse;
|
||||
}
|
||||
|
||||
frappe.model.set_value(item.doctype, item.name, update_values);
|
||||
}
|
||||
new erpnext.SerialBatchPackageSelector(me.frm, item, (r) => {
|
||||
if (r) {
|
||||
let qty = Math.abs(r.total_qty);
|
||||
if (doc.is_return) {
|
||||
qty = qty * -1;
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let update_values = {
|
||||
serial_and_batch_bundle: r.name,
|
||||
use_serial_batch_fields: 0,
|
||||
qty:
|
||||
qty /
|
||||
flt(
|
||||
item.conversion_factor || 1,
|
||||
precision("conversion_factor", item)
|
||||
),
|
||||
};
|
||||
|
||||
if (r.warehouse) {
|
||||
update_values["warehouse"] = r.warehouse;
|
||||
}
|
||||
|
||||
frappe.model.set_value(item.doctype, item.name, update_values);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
add_serial_batch_for_rejected_qty(doc, cdt, cdn) {
|
||||
@@ -425,142 +476,147 @@ erpnext.buying = {
|
||||
let me = this;
|
||||
let fields = ["has_batch_no", "has_serial_no"];
|
||||
|
||||
frappe.db.get_value("Item", item.item_code, fields)
|
||||
.then((r) => {
|
||||
if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) {
|
||||
fields.forEach((field) => {
|
||||
item[field] = r.message[field];
|
||||
});
|
||||
frappe.db.get_value("Item", item.item_code, fields).then((r) => {
|
||||
if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) {
|
||||
fields.forEach((field) => {
|
||||
item[field] = r.message[field];
|
||||
});
|
||||
|
||||
item.type_of_transaction = !doc.is_return > 0 ? "Inward" : "Outward";
|
||||
item.is_rejected = true;
|
||||
item.type_of_transaction = !doc.is_return > 0 ? "Inward" : "Outward";
|
||||
item.is_rejected = true;
|
||||
|
||||
new erpnext.SerialBatchPackageSelector(
|
||||
me.frm, item, (r) => {
|
||||
if (r) {
|
||||
let qty = Math.abs(r.total_qty);
|
||||
if (doc.is_return) {
|
||||
qty = qty * -1;
|
||||
}
|
||||
|
||||
let update_values = {
|
||||
"rejected_serial_and_batch_bundle": r.name,
|
||||
"use_serial_batch_fields": 0,
|
||||
"rejected_qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item))
|
||||
}
|
||||
|
||||
if (r.warehouse) {
|
||||
update_values["rejected_warehouse"] = r.warehouse;
|
||||
}
|
||||
|
||||
frappe.model.set_value(item.doctype, item.name, update_values);
|
||||
}
|
||||
new erpnext.SerialBatchPackageSelector(me.frm, item, (r) => {
|
||||
if (r) {
|
||||
let qty = Math.abs(r.total_qty);
|
||||
if (doc.is_return) {
|
||||
qty = qty * -1;
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let update_values = {
|
||||
rejected_serial_and_batch_bundle: r.name,
|
||||
use_serial_batch_fields: 0,
|
||||
rejected_qty:
|
||||
qty /
|
||||
flt(
|
||||
item.conversion_factor || 1,
|
||||
precision("conversion_factor", item)
|
||||
),
|
||||
};
|
||||
|
||||
if (r.warehouse) {
|
||||
update_values["rejected_warehouse"] = r.warehouse;
|
||||
}
|
||||
|
||||
frappe.model.set_value(item.doctype, item.name, update_values);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
erpnext.buying.link_to_mrs = function(frm) {
|
||||
erpnext.buying.link_to_mrs = function (frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.buying.utils.get_linked_material_requests",
|
||||
args:{
|
||||
items: frm.doc.items.map((item) => item.item_code)
|
||||
args: {
|
||||
items: frm.doc.items.map((item) => item.item_code),
|
||||
},
|
||||
callback: function(r) {
|
||||
callback: function (r) {
|
||||
if (!r.message || r.message.length == 0) {
|
||||
frappe.throw({
|
||||
message: __("No pending Material Requests found to link for the given items."),
|
||||
title: __("Note")
|
||||
title: __("Note"),
|
||||
});
|
||||
}
|
||||
|
||||
var item_length = frm.doc.items.length;
|
||||
for (let item of frm.doc.items) {
|
||||
var qty = item.qty;
|
||||
(r.message[0] || []).forEach(function(d) {
|
||||
if (d.qty > 0 && qty > 0 && item.item_code == d.item_code && !item.material_request_item)
|
||||
{
|
||||
(r.message[0] || []).forEach(function (d) {
|
||||
if (
|
||||
d.qty > 0 &&
|
||||
qty > 0 &&
|
||||
item.item_code == d.item_code &&
|
||||
!item.material_request_item
|
||||
) {
|
||||
item.material_request = d.mr_name;
|
||||
item.material_request_item = d.mr_item;
|
||||
var my_qty = Math.min(qty, d.qty);
|
||||
qty = qty - my_qty;
|
||||
d.qty = d.qty - my_qty;
|
||||
item.stock_qty = my_qty*item.conversion_factor;
|
||||
item.stock_qty = my_qty * item.conversion_factor;
|
||||
item.qty = my_qty;
|
||||
|
||||
frappe.msgprint("Assigning " + d.mr_name + " to " + d.item_code + " (row " + item.idx + ")");
|
||||
if (qty > 0)
|
||||
{
|
||||
frappe.msgprint(
|
||||
"Assigning " + d.mr_name + " to " + d.item_code + " (row " + item.idx + ")"
|
||||
);
|
||||
if (qty > 0) {
|
||||
frappe.msgprint("Splitting " + qty + " units of " + d.item_code);
|
||||
var newrow = frappe.model.add_child(frm.doc, item.doctype, "items");
|
||||
item_length++;
|
||||
|
||||
for (var key in item)
|
||||
{
|
||||
for (var key in item) {
|
||||
newrow[key] = item[key];
|
||||
}
|
||||
|
||||
newrow.idx = item_length;
|
||||
newrow["stock_qty"] = newrow.conversion_factor*qty;
|
||||
newrow["stock_qty"] = newrow.conversion_factor * qty;
|
||||
newrow["qty"] = qty;
|
||||
|
||||
newrow["material_request"] = "";
|
||||
newrow["material_request_item"] = "";
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
refresh_field("items");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
erpnext.buying.get_default_bom = function(frm) {
|
||||
$.each(frm.doc["items"] || [], function(i, d) {
|
||||
erpnext.buying.get_default_bom = function (frm) {
|
||||
$.each(frm.doc["items"] || [], function (i, d) {
|
||||
if (d.item_code && d.bom === "") {
|
||||
return frappe.call({
|
||||
type: "GET",
|
||||
method: "erpnext.stock.get_item_details.get_default_bom",
|
||||
args: {
|
||||
"item_code": d.item_code,
|
||||
item_code: d.item_code,
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r) {
|
||||
callback: function (r) {
|
||||
if (r) {
|
||||
frappe.model.set_value(d.doctype, d.name, "bom", r.message);
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
erpnext.buying.get_items_from_product_bundle = function(frm) {
|
||||
erpnext.buying.get_items_from_product_bundle = function (frm) {
|
||||
var dialog = new frappe.ui.Dialog({
|
||||
title: __("Get Items from Product Bundle"),
|
||||
fields: [
|
||||
{
|
||||
"fieldtype": "Link",
|
||||
"label": __("Product Bundle"),
|
||||
"fieldname": "product_bundle",
|
||||
"options":"Product Bundle",
|
||||
"reqd": 1
|
||||
fieldtype: "Link",
|
||||
label: __("Product Bundle"),
|
||||
fieldname: "product_bundle",
|
||||
options: "Product Bundle",
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
"fieldtype": "Currency",
|
||||
"label": __("Quantity"),
|
||||
"fieldname": "quantity",
|
||||
"reqd": 1,
|
||||
"default": 1
|
||||
}
|
||||
fieldtype: "Currency",
|
||||
label: __("Quantity"),
|
||||
fieldname: "quantity",
|
||||
reqd: 1,
|
||||
default: 1,
|
||||
},
|
||||
],
|
||||
primary_action_label: 'Get Items',
|
||||
primary_action(args){
|
||||
if(!args) return;
|
||||
primary_action_label: "Get Items",
|
||||
primary_action(args) {
|
||||
if (!args) return;
|
||||
dialog.hide();
|
||||
return frappe.call({
|
||||
type: "GET",
|
||||
@@ -581,44 +637,44 @@ erpnext.buying.get_items_from_product_bundle = function(frm) {
|
||||
is_subcontracted: frm.doc.is_subcontracted,
|
||||
transaction_date: frm.doc.transaction_date || frm.doc.posting_date,
|
||||
ignore_pricing_rule: frm.doc.ignore_pricing_rule,
|
||||
doctype: frm.doc.doctype
|
||||
doctype: frm.doc.doctype,
|
||||
},
|
||||
},
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
const first_row_is_empty = function(child_table){
|
||||
if($.isArray(child_table) && child_table.length > 0) {
|
||||
callback: function (r) {
|
||||
const first_row_is_empty = function (child_table) {
|
||||
if ($.isArray(child_table) && child_table.length > 0) {
|
||||
return !child_table[0].item_code;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const remove_empty_first_row = function(frm){
|
||||
if (first_row_is_empty(frm.doc.items)){
|
||||
frm.doc.items = frm.doc.items.splice(1);
|
||||
const remove_empty_first_row = function (frm) {
|
||||
if (first_row_is_empty(frm.doc.items)) {
|
||||
frm.doc.items = frm.doc.items.splice(1);
|
||||
}
|
||||
};
|
||||
|
||||
if(!r.exc && r.message) {
|
||||
if (!r.exc && r.message) {
|
||||
remove_empty_first_row(frm);
|
||||
for (var i=0; i< r.message.length; i++) {
|
||||
for (var i = 0; i < r.message.length; i++) {
|
||||
var d = frm.add_child("items");
|
||||
var item = r.message[i];
|
||||
for (var key in item) {
|
||||
for (var key in item) {
|
||||
if (!is_null(item[key]) && key !== "doctype") {
|
||||
d[key] = item[key];
|
||||
}
|
||||
}
|
||||
if(frappe.meta.get_docfield(d.doctype, "price_list_rate", d.name)) {
|
||||
if (frappe.meta.get_docfield(d.doctype, "price_list_rate", d.name)) {
|
||||
frm.script_manager.trigger("price_list_rate", d.doctype, d.name);
|
||||
}
|
||||
}
|
||||
frm.refresh_field("items");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -11,13 +11,13 @@ erpnext.stock.StockController = class StockController extends frappe.ui.form.Con
|
||||
}
|
||||
}
|
||||
|
||||
barcode(doc, cdt, cdn) {
|
||||
barcode(doc, cdt, cdn) {
|
||||
let row = locals[cdt][cdn];
|
||||
if (row.barcode) {
|
||||
erpnext.stock.utils.set_item_details_using_barcode(this.frm, row, (r) => {
|
||||
frappe.model.set_value(cdt, cdn, {
|
||||
"item_code": r.message.item_code,
|
||||
"qty": 1,
|
||||
item_code: r.message.item_code,
|
||||
qty: 1,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -25,74 +25,81 @@ erpnext.stock.StockController = class StockController extends frappe.ui.form.Con
|
||||
|
||||
setup_warehouse_query() {
|
||||
var me = this;
|
||||
erpnext.queries.setup_queries(this.frm, "Warehouse", function() {
|
||||
erpnext.queries.setup_queries(this.frm, "Warehouse", function () {
|
||||
return erpnext.queries.warehouse(me.frm.doc);
|
||||
});
|
||||
}
|
||||
|
||||
setup_posting_date_time_check() {
|
||||
// make posting date default and read only unless explictly checked
|
||||
frappe.ui.form.on(this.frm.doctype, 'set_posting_date_and_time_read_only', function(frm) {
|
||||
if(frm.doc.docstatus == 0 && frm.doc.set_posting_time) {
|
||||
frm.set_df_property('posting_date', 'read_only', 0);
|
||||
frm.set_df_property('posting_time', 'read_only', 0);
|
||||
frappe.ui.form.on(this.frm.doctype, "set_posting_date_and_time_read_only", function (frm) {
|
||||
if (frm.doc.docstatus == 0 && frm.doc.set_posting_time) {
|
||||
frm.set_df_property("posting_date", "read_only", 0);
|
||||
frm.set_df_property("posting_time", "read_only", 0);
|
||||
} else {
|
||||
frm.set_df_property('posting_date', 'read_only', 1);
|
||||
frm.set_df_property('posting_time', 'read_only', 1);
|
||||
frm.set_df_property("posting_date", "read_only", 1);
|
||||
frm.set_df_property("posting_time", "read_only", 1);
|
||||
}
|
||||
})
|
||||
|
||||
frappe.ui.form.on(this.frm.doctype, 'set_posting_time', function(frm) {
|
||||
frm.trigger('set_posting_date_and_time_read_only');
|
||||
});
|
||||
|
||||
frappe.ui.form.on(this.frm.doctype, 'refresh', function(frm) {
|
||||
frappe.ui.form.on(this.frm.doctype, "set_posting_time", function (frm) {
|
||||
frm.trigger("set_posting_date_and_time_read_only");
|
||||
});
|
||||
|
||||
frappe.ui.form.on(this.frm.doctype, "refresh", function (frm) {
|
||||
// set default posting date / time
|
||||
if(frm.doc.docstatus==0) {
|
||||
if(!frm.doc.posting_date) {
|
||||
frm.set_value('posting_date', frappe.datetime.nowdate());
|
||||
if (frm.doc.docstatus == 0) {
|
||||
if (!frm.doc.posting_date) {
|
||||
frm.set_value("posting_date", frappe.datetime.nowdate());
|
||||
}
|
||||
if(!frm.doc.posting_time) {
|
||||
frm.set_value('posting_time', frappe.datetime.now_time());
|
||||
if (!frm.doc.posting_time) {
|
||||
frm.set_value("posting_time", frappe.datetime.now_time());
|
||||
}
|
||||
frm.trigger('set_posting_date_and_time_read_only');
|
||||
frm.trigger("set_posting_date_and_time_read_only");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
show_stock_ledger() {
|
||||
var me = this;
|
||||
if(this.frm.doc.docstatus > 0) {
|
||||
cur_frm.add_custom_button(__("Stock Ledger"), function() {
|
||||
frappe.route_options = {
|
||||
voucher_no: me.frm.doc.name,
|
||||
from_date: me.frm.doc.posting_date,
|
||||
to_date: moment(me.frm.doc.modified).format('YYYY-MM-DD'),
|
||||
company: me.frm.doc.company,
|
||||
show_cancelled_entries: me.frm.doc.docstatus === 2,
|
||||
ignore_prepared_report: true
|
||||
};
|
||||
frappe.set_route("query-report", "Stock Ledger");
|
||||
}, __("View"));
|
||||
if (this.frm.doc.docstatus > 0) {
|
||||
cur_frm.add_custom_button(
|
||||
__("Stock Ledger"),
|
||||
function () {
|
||||
frappe.route_options = {
|
||||
voucher_no: me.frm.doc.name,
|
||||
from_date: me.frm.doc.posting_date,
|
||||
to_date: moment(me.frm.doc.modified).format("YYYY-MM-DD"),
|
||||
company: me.frm.doc.company,
|
||||
show_cancelled_entries: me.frm.doc.docstatus === 2,
|
||||
ignore_prepared_report: true,
|
||||
};
|
||||
frappe.set_route("query-report", "Stock Ledger");
|
||||
},
|
||||
__("View")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
show_general_ledger() {
|
||||
let me = this;
|
||||
if(this.frm.doc.docstatus > 0) {
|
||||
cur_frm.add_custom_button(__('Accounting Ledger'), function() {
|
||||
frappe.route_options = {
|
||||
voucher_no: me.frm.doc.name,
|
||||
from_date: me.frm.doc.posting_date,
|
||||
to_date: moment(me.frm.doc.modified).format('YYYY-MM-DD'),
|
||||
company: me.frm.doc.company,
|
||||
categorize_by: "Categorize by Voucher (Consolidated)",
|
||||
show_cancelled_entries: me.frm.doc.docstatus === 2,
|
||||
ignore_prepared_report: true
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
}, __("View"));
|
||||
if (this.frm.doc.docstatus > 0) {
|
||||
cur_frm.add_custom_button(
|
||||
__("Accounting Ledger"),
|
||||
function () {
|
||||
frappe.route_options = {
|
||||
voucher_no: me.frm.doc.name,
|
||||
from_date: me.frm.doc.posting_date,
|
||||
to_date: moment(me.frm.doc.modified).format("YYYY-MM-DD"),
|
||||
company: me.frm.doc.company,
|
||||
categorize_by: "Categorize by Voucher (Consolidated)",
|
||||
show_cancelled_entries: me.frm.doc.docstatus === 2,
|
||||
ignore_prepared_report: true,
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
},
|
||||
__("View")
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1828,7 +1828,7 @@ class TestSalesOrder(AccountsTestMixin, IntegrationTestCase):
|
||||
wo.reload()
|
||||
so.reload()
|
||||
self.assertEqual(so.items[0].work_order_qty, wo.produced_qty)
|
||||
self.assertEqual(mr.status, "Manufactured")
|
||||
self.assertEqual(mr.status, "Ordered")
|
||||
|
||||
def test_sales_order_with_shipping_rule(self):
|
||||
from erpnext.accounts.doctype.shipping_rule.test_shipping_rule import create_shipping_rule
|
||||
|
||||
@@ -450,7 +450,6 @@ erpnext.PointOfSale.Payment = class {
|
||||
}
|
||||
|
||||
render_payment_section() {
|
||||
this.grand_total_to_default_mop();
|
||||
this.render_payment_mode_dom();
|
||||
this.make_invoice_field_dialog();
|
||||
this.update_totals_section();
|
||||
@@ -498,17 +497,6 @@ erpnext.PointOfSale.Payment = class {
|
||||
}
|
||||
}
|
||||
|
||||
grand_total_to_default_mop() {
|
||||
if (this.set_gt_to_default_mop) return;
|
||||
const doc = this.events.get_frm().doc;
|
||||
const payments = doc.payments;
|
||||
payments.forEach((p) => {
|
||||
if (p.default) {
|
||||
frappe.model.set_value(p.doctype, p.name, "amount", 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render_payment_mode_dom() {
|
||||
const doc = this.events.get_frm().doc;
|
||||
const payments = doc.payments;
|
||||
|
||||
@@ -294,6 +294,10 @@ def install(country=None):
|
||||
{"doctype": "Market Segment", "market_segment": _("Upper Income")},
|
||||
# Warehouse Type
|
||||
{"doctype": "Warehouse Type", "name": "Transit"},
|
||||
{"doctype": "Workstation Operating Component", "component_name": _("Electricity")},
|
||||
{"doctype": "Workstation Operating Component", "component_name": _("Consumables")},
|
||||
{"doctype": "Workstation Operating Component", "component_name": _("Rent")},
|
||||
{"doctype": "Workstation Operating Component", "component_name": _("Wages")},
|
||||
]
|
||||
|
||||
for doctype, title_field, filename in (
|
||||
@@ -481,14 +485,19 @@ def install_defaults(args=None): # nosemgrep
|
||||
create_bank_account(args)
|
||||
|
||||
|
||||
def set_global_defaults(args):
|
||||
def set_global_defaults(kwargs):
|
||||
global_defaults = frappe.get_doc("Global Defaults", "Global Defaults")
|
||||
company = frappe.db.get_value(
|
||||
"Company",
|
||||
{"company_name": kwargs.get("company_name")},
|
||||
"name",
|
||||
)
|
||||
|
||||
global_defaults.update(
|
||||
{
|
||||
"default_currency": args.get("currency"),
|
||||
"default_company": args.get("company_name"),
|
||||
"country": args.get("country"),
|
||||
"default_currency": kwargs.get("currency"),
|
||||
"default_company": company,
|
||||
"country": kwargs.get("country"),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -667,13 +667,13 @@ def prepare_data_for_internal_transfer():
|
||||
company = "_Test Company with perpetual inventory"
|
||||
|
||||
customer = create_internal_customer(
|
||||
"_Test Internal Customer 3",
|
||||
"_Test Internal Customer 2",
|
||||
company,
|
||||
company,
|
||||
)
|
||||
|
||||
supplier = create_internal_supplier(
|
||||
"_Test Internal Supplier 3",
|
||||
"_Test Internal Supplier 2",
|
||||
company,
|
||||
company,
|
||||
)
|
||||
|
||||
@@ -11,6 +11,7 @@ import frappe
|
||||
import frappe.defaults
|
||||
from frappe import _, msgprint
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.query_builder import Order
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import cint, cstr, flt, get_link_to_form, getdate, new_line_sep, nowdate
|
||||
|
||||
@@ -624,39 +625,44 @@ def get_items_based_on_default_supplier(supplier):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_material_requests_based_on_supplier(doctype, txt, searchfield, start, page_len, filters):
|
||||
conditions = ""
|
||||
if txt:
|
||||
conditions += "and mr.name like '%%" + txt + "%%' "
|
||||
|
||||
if filters.get("transaction_date"):
|
||||
date = filters.get("transaction_date")[1]
|
||||
conditions += f"and mr.transaction_date between '{date[0]}' and '{date[1]}' "
|
||||
|
||||
supplier = filters.get("supplier")
|
||||
supplier_items = get_items_based_on_default_supplier(supplier)
|
||||
|
||||
if not supplier_items:
|
||||
frappe.throw(_("{0} is not the default supplier for any items.").format(supplier))
|
||||
|
||||
material_requests = frappe.db.sql(
|
||||
"""select distinct mr.name, transaction_date,company
|
||||
from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
|
||||
where mr.name = mr_item.parent
|
||||
and mr_item.item_code in ({})
|
||||
and mr.material_request_type = 'Purchase'
|
||||
and mr.per_ordered < 99.99
|
||||
and mr.docstatus = 1
|
||||
and mr.status != 'Stopped'
|
||||
and mr.company = %s
|
||||
{}
|
||||
order by mr_item.item_code ASC
|
||||
limit {} offset {} """.format(
|
||||
", ".join(["%s"] * len(supplier_items)), conditions, cint(page_len), cint(start)
|
||||
),
|
||||
(*tuple(supplier_items), filters.get("company")),
|
||||
as_dict=1,
|
||||
mr = frappe.qb.DocType("Material Request")
|
||||
mr_item = frappe.qb.DocType("Material Request Item")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(mr)
|
||||
.from_(mr_item)
|
||||
.select(mr.name)
|
||||
.distinct()
|
||||
.select(mr.transaction_date, mr.company)
|
||||
.where(
|
||||
(mr.name == mr_item.parent)
|
||||
& (mr_item.item_code.isin(supplier_items))
|
||||
& (mr.material_request_type == "Purchase")
|
||||
& (mr.per_ordered < 99.99)
|
||||
& (mr.docstatus == 1)
|
||||
& (mr.status != "Stopped")
|
||||
& (mr.company == filters.get("company"))
|
||||
)
|
||||
.orderby(mr_item.item_code, order=Order.asc)
|
||||
.limit(cint(page_len))
|
||||
.offset(cint(start))
|
||||
)
|
||||
|
||||
if txt:
|
||||
query = query.where(mr.name.like(f"%%{txt}%%"))
|
||||
|
||||
if filters.get("transaction_date"):
|
||||
date = filters.get("transaction_date")[1]
|
||||
query = query.where(mr.transaction_date[date[0] : date[1]])
|
||||
|
||||
material_requests = query.run(as_dict=True)
|
||||
|
||||
return material_requests
|
||||
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ frappe.listview_settings["Material Request"] = {
|
||||
return [__("Partially Received"), "yellow", "per_received,<,100"];
|
||||
} else if (doc.material_request_type == "Purchase" && flt(doc.per_received, precision) == 100) {
|
||||
return [__("Received"), "green", "per_received,=,100"];
|
||||
} else if (doc.material_request_type == "Purchase") {
|
||||
} else if (["Purchase", "Manufacture"].includes(doc.material_request_type)) {
|
||||
return [__("Ordered"), "green", "per_ordered,=,100"];
|
||||
} else if (doc.material_request_type == "Material Transfer") {
|
||||
return [__("Transferred"), "green", "per_ordered,=,100"];
|
||||
@@ -43,8 +43,6 @@ frappe.listview_settings["Material Request"] = {
|
||||
return [__("Issued"), "green", "per_ordered,=,100"];
|
||||
} else if (doc.material_request_type == "Customer Provided") {
|
||||
return [__("Received"), "green", "per_ordered,=,100"];
|
||||
} else if (doc.material_request_type == "Manufacture") {
|
||||
return [__("Manufactured"), "green", "per_ordered,=,100"];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -916,6 +916,23 @@ class TestMaterialRequest(IntegrationTestCase):
|
||||
for perm in permissions:
|
||||
perm.delete()
|
||||
|
||||
def test_manufacture_type_status_over_wo(self):
|
||||
from erpnext.stock.doctype.material_request.material_request import raise_work_orders
|
||||
|
||||
mr = make_material_request(
|
||||
item_code="_Test FG Item", material_request_type="Manufacture", do_not_submit=False
|
||||
)
|
||||
|
||||
work_order = raise_work_orders(mr.name)
|
||||
wo = frappe.get_doc("Work Order", work_order[0])
|
||||
wo.wip_warehouse = "_Test Warehouse 1 - _TC"
|
||||
wo.submit()
|
||||
|
||||
mr.reload()
|
||||
|
||||
self.assertEqual(mr.per_ordered, 100)
|
||||
self.assertEqual(mr.status, "Ordered")
|
||||
|
||||
|
||||
def get_in_transit_warehouse(company):
|
||||
if not frappe.db.exists("Warehouse Type", "Transit"):
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user