mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-26 08:24:47 +00:00
Merge branch 'develop' into prevent-negative-repair-cost
This commit is contained in:
@@ -3,3 +3,4 @@ reviews:
|
|||||||
ignore_title_keywords:
|
ignore_title_keywords:
|
||||||
- "sync translations"
|
- "sync translations"
|
||||||
- "update POT file"
|
- "update POT file"
|
||||||
|
review_status: false
|
||||||
|
|||||||
3
.github/workflows/patch.yml
vendored
3
.github/workflows/patch.yml
vendored
@@ -8,6 +8,9 @@ on:
|
|||||||
- '**.md'
|
- '**.md'
|
||||||
- '**.html'
|
- '**.html'
|
||||||
- '**.csv'
|
- '**.csv'
|
||||||
|
- 'crowdin.yml'
|
||||||
|
- '.coderabbit.yml'
|
||||||
|
- '.mergify.yml'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
|||||||
3
.github/workflows/server-tests-mariadb.yml
vendored
3
.github/workflows/server-tests-mariadb.yml
vendored
@@ -9,6 +9,9 @@ on:
|
|||||||
- '**.css'
|
- '**.css'
|
||||||
- '**.md'
|
- '**.md'
|
||||||
- '**.html'
|
- '**.html'
|
||||||
|
- 'crowdin.yml'
|
||||||
|
- '.coderabbit.yml'
|
||||||
|
- '.mergify.yml'
|
||||||
schedule:
|
schedule:
|
||||||
# Run everday at midnight UTC / 5:30 IST
|
# Run everday at midnight UTC / 5:30 IST
|
||||||
- cron: "0 0 * * *"
|
- 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'
|
- '**.js'
|
||||||
- '**.md'
|
- '**.md'
|
||||||
- '**.html'
|
- '**.html'
|
||||||
|
- 'crowdin.yml'
|
||||||
|
- '.coderabbit.yml'
|
||||||
|
- '.mergify.yml'
|
||||||
types: [opened, labelled, synchronize, reopened]
|
types: [opened, labelled, synchronize, reopened]
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ repos:
|
|||||||
cypress/.*|
|
cypress/.*|
|
||||||
.*node_modules.*|
|
.*node_modules.*|
|
||||||
.*boilerplate.*|
|
.*boilerplate.*|
|
||||||
erpnext/public/js/controllers/.*|
|
|
||||||
erpnext/templates/pages/order.js|
|
|
||||||
erpnext/templates/includes/.*
|
erpnext/templates/includes/.*
|
||||||
)$
|
)$
|
||||||
|
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ erpnext/controllers/ @ruthra-kumar @rohitwaghchaure @mihir-kandoi
|
|||||||
erpnext/patches/ @ruthra-kumar
|
erpnext/patches/ @ruthra-kumar
|
||||||
|
|
||||||
.github/ @ruthra-kumar
|
.github/ @ruthra-kumar
|
||||||
pyproject.toml @akhilnarang
|
pyproject.toml @ruthra-kumar
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"accounting_dimension",
|
"accounting_dimension",
|
||||||
|
"fieldname",
|
||||||
"disabled",
|
"disabled",
|
||||||
"column_break_2",
|
"column_break_2",
|
||||||
"company",
|
"company",
|
||||||
@@ -90,11 +91,17 @@
|
|||||||
"fieldname": "apply_restriction_on_values",
|
"fieldname": "apply_restriction_on_values",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Apply restriction on dimension values"
|
"label": "Apply restriction on dimension values"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fieldname",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Fieldname"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-27 13:05:57.199186",
|
"modified": "2025-08-08 14:13:22.203011",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounting Dimension Filter",
|
"name": "Accounting Dimension Filter",
|
||||||
@@ -139,8 +146,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
|
"row_format": "Dynamic",
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,17 +17,16 @@ class AccountingDimensionFilter(Document):
|
|||||||
from frappe.types import DF
|
from frappe.types import DF
|
||||||
|
|
||||||
from erpnext.accounts.doctype.allowed_dimension.allowed_dimension import AllowedDimension
|
from erpnext.accounts.doctype.allowed_dimension.allowed_dimension import AllowedDimension
|
||||||
from erpnext.accounts.doctype.applicable_on_account.applicable_on_account import (
|
from erpnext.accounts.doctype.applicable_on_account.applicable_on_account import ApplicableOnAccount
|
||||||
ApplicableOnAccount,
|
|
||||||
)
|
|
||||||
|
|
||||||
accounting_dimension: DF.Literal
|
accounting_dimension: DF.Literal[None]
|
||||||
accounts: DF.Table[ApplicableOnAccount]
|
accounts: DF.Table[ApplicableOnAccount]
|
||||||
allow_or_restrict: DF.Literal["Allow", "Restrict"]
|
allow_or_restrict: DF.Literal["Allow", "Restrict"]
|
||||||
apply_restriction_on_values: DF.Check
|
apply_restriction_on_values: DF.Check
|
||||||
company: DF.Link
|
company: DF.Link
|
||||||
dimensions: DF.Table[AllowedDimension]
|
dimensions: DF.Table[AllowedDimension]
|
||||||
disabled: DF.Check
|
disabled: DF.Check
|
||||||
|
fieldname: DF.Data | None
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
def before_save(self):
|
def before_save(self):
|
||||||
@@ -37,6 +36,10 @@ class AccountingDimensionFilter(Document):
|
|||||||
self.set("dimensions", [])
|
self.set("dimensions", [])
|
||||||
|
|
||||||
def validate(self):
|
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()
|
self.validate_applicable_accounts()
|
||||||
|
|
||||||
def validate_applicable_accounts(self):
|
def validate_applicable_accounts(self):
|
||||||
@@ -71,7 +74,7 @@ def get_dimension_filter_map():
|
|||||||
"""
|
"""
|
||||||
SELECT
|
SELECT
|
||||||
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
|
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
|
FROM
|
||||||
`tabApplicable On Account` a,
|
`tabApplicable On Account` a,
|
||||||
`tabAccounting Dimension Filter` p
|
`tabAccounting Dimension Filter` p
|
||||||
@@ -86,8 +89,6 @@ def get_dimension_filter_map():
|
|||||||
dimension_filter_map = {}
|
dimension_filter_map = {}
|
||||||
|
|
||||||
for f in filters:
|
for f in filters:
|
||||||
f.fieldname = scrub(f.accounting_dimension)
|
|
||||||
|
|
||||||
build_map(
|
build_map(
|
||||||
dimension_filter_map,
|
dimension_filter_map,
|
||||||
f.fieldname,
|
f.fieldname,
|
||||||
|
|||||||
@@ -462,9 +462,8 @@ def unset_existing_data(company):
|
|||||||
"Sales Taxes and Charges Template",
|
"Sales Taxes and Charges Template",
|
||||||
"Purchase Taxes and Charges Template",
|
"Purchase Taxes and Charges Template",
|
||||||
]:
|
]:
|
||||||
frappe.db.sql(
|
dt = frappe.qb.DocType(doctype)
|
||||||
f'''delete from `tab{doctype}` where `company`="%s"''' % (company) # nosec
|
frappe.qb.from_(dt).where(dt.company == company).delete().run()
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def set_default_accounts(company):
|
def set_default_accounts(company):
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
-> Resolves dunning automatically
|
-> Resolves dunning automatically
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
@@ -163,43 +164,66 @@ class Dunning(AccountsController):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def resolve_dunning(doc, state):
|
def update_linked_dunnings(doc, previous_outstanding_amount):
|
||||||
"""
|
if (
|
||||||
Check if all payments have been made and resolve dunning, if yes. Called
|
doc.doctype != "Sales Invoice"
|
||||||
when a Payment Entry is submitted.
|
or doc.is_return
|
||||||
"""
|
or previous_outstanding_amount == doc.outstanding_amount
|
||||||
for reference in doc.references:
|
):
|
||||||
# Consider partial and full payments:
|
return
|
||||||
# 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
|
|
||||||
|
|
||||||
if reference.reference_doctype == "Sales Invoice" and (
|
to_resolve = doc.outstanding_amount < previous_outstanding_amount
|
||||||
submit_condition if doc.docstatus == 1 else cancel_condition
|
state = "Unresolved" if to_resolve else "Resolved"
|
||||||
):
|
dunnings = get_linked_dunnings_as_per_state(doc.name, state)
|
||||||
state = "Resolved" if doc.docstatus == 2 else "Unresolved"
|
if not dunnings:
|
||||||
dunnings = get_linked_dunnings_as_per_state(reference.reference_name, state)
|
return
|
||||||
|
|
||||||
for dunning in dunnings:
|
dunnings = [frappe.get_doc("Dunning", dunning.name) for dunning in dunnings]
|
||||||
resolve = True
|
invoices = set()
|
||||||
dunning = frappe.get_doc("Dunning", dunning.get("name"))
|
payment_schedule_ids = set()
|
||||||
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)
|
|
||||||
|
|
||||||
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:
|
invoice_outstanding_amounts = dict(
|
||||||
dunning.status = new_status
|
frappe.get_all(
|
||||||
dunning.save()
|
"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):
|
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(sales_invoice.status, "Overdue")
|
||||||
self.assertEqual(dunning.status, "Unresolved")
|
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):
|
def create_dunning(overdue_days, dunning_type_name=None):
|
||||||
posting_date = add_days(today(), -1 * overdue_days)
|
posting_date = add_days(today(), -1 * overdue_days)
|
||||||
|
|||||||
@@ -1796,6 +1796,14 @@ def make_inter_company_journal_entry(name, voucher_type, company):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_reverse_journal_entry(source_name, target_doc=None):
|
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
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
|
||||||
def post_process(source, target):
|
def post_process(source, target):
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.query_builder.functions import Sum
|
||||||
from frappe.utils import flt, today
|
from frappe.utils import flt, today
|
||||||
|
|
||||||
|
|
||||||
@@ -55,22 +56,30 @@ def get_loyalty_details(
|
|||||||
if not expiry_date:
|
if not expiry_date:
|
||||||
expiry_date = today()
|
expiry_date = today()
|
||||||
|
|
||||||
condition = ""
|
LoyaltyPointEntry = frappe.qb.DocType("Loyalty Point Entry")
|
||||||
if company:
|
|
||||||
condition = " and company=%s " % frappe.db.escape(company)
|
|
||||||
if not include_expired_entry:
|
|
||||||
condition += " and expiry_date>='%s' " % expiry_date
|
|
||||||
|
|
||||||
loyalty_point_details = frappe.db.sql(
|
query = (
|
||||||
f"""select sum(loyalty_points) as loyalty_points,
|
frappe.qb.from_(LoyaltyPointEntry)
|
||||||
sum(purchase_amount) as total_spent from `tabLoyalty Point Entry`
|
.select(
|
||||||
where customer=%s and loyalty_program=%s and posting_date <= %s
|
Sum(LoyaltyPointEntry.loyalty_points).as_("loyalty_points"),
|
||||||
{condition}
|
Sum(LoyaltyPointEntry.purchase_amount).as_("total_spent"),
|
||||||
group by customer""",
|
)
|
||||||
(customer, loyalty_program, expiry_date),
|
.where(
|
||||||
as_dict=1,
|
(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:
|
if loyalty_point_details:
|
||||||
return loyalty_point_details[0]
|
return loyalty_point_details[0]
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -200,9 +200,9 @@ class PaymentEntry(AccountsController):
|
|||||||
if self.difference_amount:
|
if self.difference_amount:
|
||||||
frappe.throw(_("Difference Amount must be zero"))
|
frappe.throw(_("Difference Amount must be zero"))
|
||||||
self.update_payment_requests()
|
self.update_payment_requests()
|
||||||
|
self.update_payment_schedule()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
self.update_outstanding_amounts()
|
self.update_outstanding_amounts()
|
||||||
self.update_payment_schedule()
|
|
||||||
self.set_status()
|
self.set_status()
|
||||||
|
|
||||||
def validate_for_repost(self):
|
def validate_for_repost(self):
|
||||||
@@ -303,10 +303,10 @@ class PaymentEntry(AccountsController):
|
|||||||
)
|
)
|
||||||
super().on_cancel()
|
super().on_cancel()
|
||||||
self.update_payment_requests(cancel=True)
|
self.update_payment_requests(cancel=True)
|
||||||
|
self.update_payment_schedule(cancel=1)
|
||||||
self.make_gl_entries(cancel=1)
|
self.make_gl_entries(cancel=1)
|
||||||
self.update_outstanding_amounts()
|
self.update_outstanding_amounts()
|
||||||
self.delink_advance_entry_references()
|
self.delink_advance_entry_references()
|
||||||
self.update_payment_schedule(cancel=1)
|
|
||||||
self.set_status()
|
self.set_status()
|
||||||
|
|
||||||
def update_payment_requests(self, cancel=False):
|
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);
|
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) {
|
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.meta.default_print_format = r.message.print_format || "";
|
||||||
this.frm.doc.campaign = r.message.campaign;
|
this.frm.doc.campaign = r.message.campaign;
|
||||||
this.frm.allow_print_before_pay = r.message.allow_print_before_pay;
|
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.frm.script_manager.trigger("update_stock");
|
||||||
this.calculate_taxes_and_totals();
|
this.calculate_taxes_and_totals();
|
||||||
|
|||||||
@@ -717,7 +717,13 @@ class POSInvoice(SalesInvoice):
|
|||||||
"Account", self.debit_to, "account_currency"
|
"Account", self.debit_to, "account_currency"
|
||||||
)
|
)
|
||||||
if not self.due_date and self.customer:
|
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)
|
super(SalesInvoice, self).set_missing_values(for_validate)
|
||||||
|
|
||||||
@@ -732,6 +738,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
"utm_campaign": profile.get("utm_campaign"),
|
"utm_campaign": profile.get("utm_campaign"),
|
||||||
"utm_medium": profile.get("utm_medium"),
|
"utm_medium": profile.get("utm_medium"),
|
||||||
"allow_print_before_pay": profile.get("allow_print_before_pay"),
|
"allow_print_before_pay": profile.get("allow_print_before_pay"),
|
||||||
|
"set_default_payment": profile.get("set_grand_total_to_default_mop"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@@ -174,6 +174,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.apply_on != 'Transaction'",
|
||||||
"fieldname": "is_cumulative",
|
"fieldname": "is_cumulative",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Is Cumulative"
|
"label": "Is Cumulative"
|
||||||
@@ -656,7 +657,7 @@
|
|||||||
"icon": "fa fa-gift",
|
"icon": "fa fa-gift",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-02-17 18:15:39.824639",
|
"modified": "2025-08-20 11:40:07.096854",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Pricing Rule",
|
"name": "Pricing Rule",
|
||||||
|
|||||||
@@ -93,12 +93,14 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.apply_on != 'Transaction'",
|
||||||
"fieldname": "mixed_conditions",
|
"fieldname": "mixed_conditions",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Mixed Conditions"
|
"label": "Mixed Conditions"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.apply_on != 'Transaction'",
|
||||||
"fieldname": "is_cumulative",
|
"fieldname": "is_cumulative",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Is Cumulative"
|
"label": "Is Cumulative"
|
||||||
@@ -278,7 +280,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-27 13:10:22.103686",
|
"modified": "2025-08-20 11:48:23.231081",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Promotional Scheme",
|
"name": "Promotional Scheme",
|
||||||
|
|||||||
@@ -343,7 +343,12 @@ class PurchaseInvoice(BuyingController):
|
|||||||
)
|
)
|
||||||
if not self.due_date:
|
if not self.due_date:
|
||||||
self.due_date = get_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")
|
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.script_manager.trigger("is_pos");
|
||||||
me.frm.refresh_fields();
|
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);
|
erpnext.queries.setup_warehouse_query(this.frm);
|
||||||
}
|
}
|
||||||
@@ -512,8 +519,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
|||||||
},
|
},
|
||||||
callback: function (r) {
|
callback: function (r) {
|
||||||
if (!r.exc) {
|
if (!r.exc) {
|
||||||
if (r.message && r.message.print_format) {
|
if (r.message) {
|
||||||
me.frm.pos_print_format = r.message.print_format;
|
me.frm.pos_print_format = r.message.print_format;
|
||||||
|
me.frm.set_default_payment = r.message.set_default_payment;
|
||||||
}
|
}
|
||||||
me.frm.trigger("update_stock");
|
me.frm.trigger("update_stock");
|
||||||
if (me.frm.doc.taxes_and_charges) {
|
if (me.frm.doc.taxes_and_charges) {
|
||||||
|
|||||||
@@ -764,6 +764,7 @@ class SalesInvoice(SellingController):
|
|||||||
"utm_campaign": pos.get("utm_campaign"),
|
"utm_campaign": pos.get("utm_campaign"),
|
||||||
"utm_medium": pos.get("utm_medium"),
|
"utm_medium": pos.get("utm_medium"),
|
||||||
"allow_print_before_pay": pos.get("allow_print_before_pay"),
|
"allow_print_before_pay": pos.get("allow_print_before_pay"),
|
||||||
|
"set_default_payment": pos.get("set_grand_total_to_default_mop", 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@@ -25,17 +25,25 @@ def get_group_by_asset_category_data(filters):
|
|||||||
|
|
||||||
asset_categories = get_asset_categories_for_grouped_by_category(filters)
|
asset_categories = get_asset_categories_for_grouped_by_category(filters)
|
||||||
assets = get_assets_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:
|
for asset_category in asset_categories:
|
||||||
row = frappe._dict()
|
row = frappe._dict()
|
||||||
row.update(asset_category)
|
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 = (
|
row.value_as_on_to_date = (
|
||||||
flt(row.value_as_on_from_date)
|
flt(row.value_as_on_from_date)
|
||||||
+ flt(row.value_of_new_purchase)
|
+ flt(row.value_of_new_purchase)
|
||||||
- flt(row.value_of_sold_asset)
|
- flt(row.value_of_sold_asset)
|
||||||
- flt(row.value_of_scrapped_asset)
|
- flt(row.value_of_scrapped_asset)
|
||||||
- flt(row.value_of_capitalized_asset)
|
- flt(row.value_of_capitalized_asset)
|
||||||
|
+ flt(row.adjustment_during_period)
|
||||||
)
|
)
|
||||||
|
|
||||||
row.update(
|
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):
|
def get_group_by_asset_data(filters):
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
asset_details = get_asset_details_for_grouped_by_category(filters)
|
asset_details = get_asset_details_for_grouped_by_category(filters)
|
||||||
assets = get_assets_for_grouped_by_asset(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:
|
for asset_detail in asset_details:
|
||||||
row = frappe._dict()
|
row = frappe._dict()
|
||||||
row.update(asset_detail)
|
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 = (
|
row.value_as_on_to_date = (
|
||||||
flt(row.value_as_on_from_date)
|
flt(row.value_as_on_from_date)
|
||||||
+ flt(row.value_of_new_purchase)
|
+ flt(row.value_of_new_purchase)
|
||||||
- flt(row.value_of_sold_asset)
|
- flt(row.value_of_sold_asset)
|
||||||
- flt(row.value_of_scrapped_asset)
|
- flt(row.value_of_scrapped_asset)
|
||||||
- flt(row.value_of_capitalized_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 = (
|
row.accumulated_depreciation_as_on_to_date = (
|
||||||
flt(row.accumulated_depreciation_as_on_from_date)
|
flt(row.accumulated_depreciation_as_on_from_date)
|
||||||
+ flt(row.depreciation_amount_during_the_period)
|
+ 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):
|
def get_columns(filters):
|
||||||
columns = []
|
columns = []
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,34 @@
|
|||||||
{
|
{
|
||||||
"add_total_row": 0,
|
"add_total_row": 1,
|
||||||
"apply_user_permissions": 1,
|
"add_translate_data": 0,
|
||||||
"creation": "2013-12-06 13:22:23",
|
"columns": [],
|
||||||
"disabled": 0,
|
"creation": "2013-12-06 13:22:23",
|
||||||
"docstatus": 0,
|
"disabled": 0,
|
||||||
"doctype": "Report",
|
"docstatus": 0,
|
||||||
"idx": 3,
|
"doctype": "Report",
|
||||||
"is_standard": "Yes",
|
"filters": [],
|
||||||
"modified": "2017-02-24 20:17:51.995451",
|
"idx": 3,
|
||||||
"modified_by": "Administrator",
|
"is_standard": "Yes",
|
||||||
"module": "Accounts",
|
"letterhead": null,
|
||||||
"name": "General Ledger",
|
"modified": "2025-08-13 12:47:27.645023",
|
||||||
"owner": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"ref_doctype": "GL Entry",
|
"module": "Accounts",
|
||||||
"report_name": "General Ledger",
|
"name": "General Ledger",
|
||||||
"report_type": "Script Report",
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "GL Entry",
|
||||||
|
"report_name": "General Ledger",
|
||||||
|
"report_type": "Script Report",
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"role": "Accounts User"
|
"role": "Accounts User"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "Accounts Manager"
|
"role": "Accounts Manager"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "Auditor"
|
"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"))
|
query = query.where(si.posting_date <= filters.get("to_date"))
|
||||||
|
|
||||||
if filters.get("mode_of_payment"):
|
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 filters.get("warehouse"):
|
||||||
if frappe.db.get_value("Warehouse", filters.get("warehouse"), "is_group"):
|
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)
|
frappe.qb.from_(si)
|
||||||
.join(sii)
|
.join(sii)
|
||||||
.on(si.name == sii.parent)
|
.on(si.name == sii.parent)
|
||||||
.left_join(sip)
|
|
||||||
.on(sip.parent == si.name)
|
|
||||||
.left_join(item)
|
.left_join(item)
|
||||||
.on(sii.item_code == item.name)
|
.on(sii.item_code == item.name)
|
||||||
.select(
|
.select(
|
||||||
@@ -466,7 +470,6 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
|
|||||||
si.update_stock,
|
si.update_stock,
|
||||||
sii.uom,
|
sii.uom,
|
||||||
sii.qty,
|
sii.qty,
|
||||||
sip.mode_of_payment,
|
|
||||||
)
|
)
|
||||||
.where(si.docstatus == 1)
|
.where(si.docstatus == 1)
|
||||||
.where(sii.parenttype == doctype)
|
.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):
|
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:
|
if not voucher_type or not voucher_no:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1969,6 +1971,7 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa
|
|||||||
|
|
||||||
outstanding = voucher_outstanding[0]
|
outstanding = voucher_outstanding[0]
|
||||||
ref_doc = frappe.get_lazy_doc(voucher_type, voucher_no)
|
ref_doc = frappe.get_lazy_doc(voucher_type, voucher_no)
|
||||||
|
previous_outstanding_amount = ref_doc.outstanding_amount
|
||||||
outstanding_amount = flt(
|
outstanding_amount = flt(
|
||||||
outstanding["outstanding_in_account_currency"], ref_doc.precision("outstanding_amount")
|
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,
|
outstanding_amount,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
update_linked_dunnings(ref_doc, previous_outstanding_amount)
|
||||||
ref_doc.set_status(update=True)
|
ref_doc.set_status(update=True)
|
||||||
ref_doc.notify_update()
|
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):
|
def make_journal_entry(asset_name):
|
||||||
asset = frappe.get_doc("Asset", asset_name)
|
asset = frappe.get_doc("Asset", asset_name)
|
||||||
(
|
(
|
||||||
_,
|
fixed_asset_account,
|
||||||
accumulated_depreciation_account,
|
accumulated_depreciation_account,
|
||||||
depreciation_expense_account,
|
depreciation_expense_account,
|
||||||
) = get_depreciation_accounts(asset.asset_category, asset.company)
|
) = get_depreciation_accounts(asset.asset_category, asset.company)
|
||||||
|
|||||||
@@ -627,6 +627,9 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
|||||||
docstatus: 1,
|
docstatus: 1,
|
||||||
status: ["not in", ["Stopped", "Expired"]],
|
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")
|
__("Get Items From")
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from frappe import _
|
|||||||
from frappe.core.doctype.communication.email import make
|
from frappe.core.doctype.communication.email import make
|
||||||
from frappe.desk.form.load import get_attachments
|
from frappe.desk.form.load import get_attachments
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
from frappe.query_builder import Order
|
||||||
from frappe.utils import get_url
|
from frappe.utils import get_url
|
||||||
from frappe.utils.print_format import download_pdf
|
from frappe.utils.print_format import download_pdf
|
||||||
from frappe.utils.user import get_user_fullname
|
from frappe.utils.user import get_user_fullname
|
||||||
@@ -582,35 +583,32 @@ def get_supplier_tag():
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filters):
|
def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filters):
|
||||||
conditions = ""
|
rfq = frappe.qb.DocType("Request for Quotation")
|
||||||
if txt:
|
rfq_supplier = frappe.qb.DocType("Request for Quotation Supplier")
|
||||||
conditions += "and rfq.name like '%%" + txt + "%%' "
|
|
||||||
|
|
||||||
if filters.get("transaction_date"):
|
query = (
|
||||||
conditions += "and rfq.transaction_date = '{}'".format(filters.get("transaction_date"))
|
frappe.qb.from_(rfq)
|
||||||
|
.from_(rfq_supplier)
|
||||||
rfq_data = frappe.db.sql(
|
.select(rfq.name)
|
||||||
f"""
|
.distinct()
|
||||||
select
|
.select(rfq.transaction_date, rfq.company)
|
||||||
distinct rfq.name, rfq.transaction_date,
|
.where(
|
||||||
rfq.company
|
(rfq.name == rfq_supplier.parent)
|
||||||
from
|
& (rfq_supplier.supplier == filters.get("supplier"))
|
||||||
`tabRequest for Quotation` rfq, `tabRequest for Quotation Supplier` rfq_supplier
|
& (rfq.docstatus == 1)
|
||||||
where
|
& (rfq.company == filters.get("company"))
|
||||||
rfq.name = rfq_supplier.parent
|
)
|
||||||
and rfq_supplier.supplier = %(supplier)s
|
.orderby(rfq.transaction_date, order=Order.asc)
|
||||||
and rfq.docstatus = 1
|
.limit(page_len)
|
||||||
and rfq.company = %(company)s
|
.offset(start)
|
||||||
{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,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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
|
return rfq_data
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
@@ -235,7 +237,12 @@ def get_list_context(context=None):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@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):
|
def set_missing_values(source, target):
|
||||||
target.run_method("set_missing_values")
|
target.run_method("set_missing_values")
|
||||||
target.run_method("get_schedule_dates")
|
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):
|
def update_item(obj, target, source_parent):
|
||||||
target.stock_qty = flt(obj.qty) * flt(obj.conversion_factor)
|
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(
|
doclist = get_mapped_doc(
|
||||||
"Supplier Quotation",
|
"Supplier Quotation",
|
||||||
source_name,
|
source_name,
|
||||||
@@ -265,6 +277,7 @@ def make_purchase_order(source_name, target_doc=None):
|
|||||||
["sales_order", "sales_order"],
|
["sales_order", "sales_order"],
|
||||||
],
|
],
|
||||||
"postprocess": update_item,
|
"postprocess": update_item,
|
||||||
|
"condition": select_item,
|
||||||
},
|
},
|
||||||
"Purchase Taxes and Charges": {
|
"Purchase Taxes and Charges": {
|
||||||
"doctype": "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.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
|
def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
|
||||||
return frappe.db.sql(
|
bo = frappe.qb.DocType("Blanket Order")
|
||||||
"""select distinct bo.name, bo.blanket_order_type, bo.to_date
|
bo_item = frappe.qb.DocType("Blanket Order Item")
|
||||||
from `tabBlanket Order` bo, `tabBlanket Order Item` boi
|
|
||||||
where
|
blanket_orders = (
|
||||||
boi.parent = bo.name
|
frappe.qb.from_(bo)
|
||||||
and boi.item_code = {item_code}
|
.from_(bo_item)
|
||||||
and bo.blanket_order_type = '{blanket_order_type}'
|
.select(bo.name)
|
||||||
and bo.company = {company}
|
.distinct()
|
||||||
and bo.docstatus = 1""".format(
|
.select(bo.blanket_order_type, bo.to_date)
|
||||||
item_code=frappe.db.escape(filters.get("item")),
|
.where(
|
||||||
blanket_order_type=filters.get("blanket_order_type"),
|
(bo_item.parent == bo.name)
|
||||||
company=frappe.db.escape(filters.get("company")),
|
& (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.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@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"):
|
if filters.get("company"):
|
||||||
condition += "and tabAccount.company = %(company)s"
|
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(
|
return frappe.db.sql(
|
||||||
f"""select tabAccount.name from `tabAccount`
|
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
|
and tabAccount.`{searchfield}` LIKE %(txt)s
|
||||||
{condition} {get_match_cond(doctype)}
|
{condition} {get_match_cond(doctype)}
|
||||||
order by idx desc, name""",
|
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"],
|
["Pending", "eval:self.status != 'Stopped' and self.per_ordered == 0 and self.docstatus == 1"],
|
||||||
[
|
[
|
||||||
"Ordered",
|
"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",
|
"Transferred",
|
||||||
@@ -142,10 +142,6 @@ status_map = {
|
|||||||
"Partially Ordered",
|
"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'",
|
"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": [
|
"POS Opening Entry": [
|
||||||
["Draft", None],
|
["Draft", None],
|
||||||
|
|||||||
@@ -1963,6 +1963,7 @@ def make_bundle_for_material_transfer(**kwargs):
|
|||||||
|
|
||||||
row.warehouse = kwargs.warehouse
|
row.warehouse = kwargs.warehouse
|
||||||
|
|
||||||
|
bundle_doc.set_incoming_rate()
|
||||||
bundle_doc.calculate_qty_and_amount()
|
bundle_doc.calculate_qty_and_amount()
|
||||||
bundle_doc.flags.ignore_permissions = True
|
bundle_doc.flags.ignore_permissions = True
|
||||||
bundle_doc.flags.ignore_validate = True
|
bundle_doc.flags.ignore_validate = True
|
||||||
|
|||||||
@@ -360,7 +360,9 @@ doc_events = {
|
|||||||
"erpnext.regional.create_transaction_log",
|
"erpnext.regional.create_transaction_log",
|
||||||
"erpnext.regional.italy.utils.sales_invoice_on_submit",
|
"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",
|
"on_trash": "erpnext.regional.check_deletion_permission",
|
||||||
},
|
},
|
||||||
"Purchase Invoice": {
|
"Purchase Invoice": {
|
||||||
@@ -372,9 +374,7 @@ doc_events = {
|
|||||||
"Payment Entry": {
|
"Payment Entry": {
|
||||||
"on_submit": [
|
"on_submit": [
|
||||||
"erpnext.regional.create_transaction_log",
|
"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",
|
"on_trash": "erpnext.regional.check_deletion_permission",
|
||||||
},
|
},
|
||||||
"Address": {
|
"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,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
expecnse_account = (
|
expense_account = (
|
||||||
company_account.default_operating_cost_account or company_account.default_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_non_stock_items_cost(stock_entry, work_order, expense_account)
|
||||||
add_operations_cost(stock_entry, work_order, expecnse_account)
|
add_operations_cost(stock_entry, work_order, expense_account)
|
||||||
|
|
||||||
|
|
||||||
def add_non_stock_items_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):
|
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
|
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)
|
operating_cost_per_unit = get_operating_cost_per_unit(work_order, stock_entry.bom_no)
|
||||||
|
|
||||||
if operating_cost_per_unit:
|
if operating_cost_per_unit:
|
||||||
stock_entry.append(
|
cost_added = add_operating_cost_component_wise(
|
||||||
"additional_costs",
|
stock_entry, work_order, operating_cost_per_unit, expense_account
|
||||||
{
|
|
||||||
"expense_account": expense_account,
|
|
||||||
"description": _("Operating Cost as per Work Order / BOM"),
|
|
||||||
"amount": operating_cost_per_unit * flt(stock_entry.fg_completed_qty),
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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:
|
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)
|
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, "source_warehouse");
|
||||||
frm.events.set_company_filters(frm, "wip_warehouse");
|
frm.events.set_company_filters(frm, "wip_warehouse");
|
||||||
frm.set_query("source_warehouse", "items", () => {
|
frm.set_query("source_warehouse", "items", () => {
|
||||||
@@ -184,7 +194,12 @@ frappe.ui.form.on("Job Card", {
|
|||||||
!frm.doc.finished_good ||
|
!frm.doc.finished_good ||
|
||||||
!has_items?.length)
|
!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"), () => {
|
frm.add_custom_button(__("Start Job"), () => {
|
||||||
let from_time = frappe.datetime.now_datetime();
|
let from_time = frappe.datetime.now_datetime();
|
||||||
if ((frm.doc.employee && !frm.doc.employee.length) || !frm.doc.employee) {
|
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);
|
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({
|
fields.push({
|
||||||
fieldtype: "Datetime",
|
fieldtype: "Datetime",
|
||||||
label: __("End Time"),
|
label: __("End Time"),
|
||||||
@@ -758,3 +778,7 @@ function get_last_completed_row(time_logs) {
|
|||||||
return last_completed_row;
|
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.validate_sequence_id()
|
||||||
self.set_sub_operations()
|
self.set_sub_operations()
|
||||||
self.update_sub_operation_status()
|
self.update_sub_operation_status()
|
||||||
|
if self.sub_operations:
|
||||||
|
self.set_total_completed_qty_from_sub_operations()
|
||||||
|
|
||||||
self.validate_work_order()
|
self.validate_work_order()
|
||||||
|
|
||||||
def on_update(self):
|
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:
|
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):
|
def get_overlap_for(self, args, open_job_cards=None):
|
||||||
time_logs = []
|
time_logs = []
|
||||||
@@ -613,7 +621,7 @@ class JobCard(Document):
|
|||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def update_sub_operation_status(self):
|
def update_sub_operation_status(self):
|
||||||
if not (self.sub_operations and self.time_logs):
|
if not self.sub_operations:
|
||||||
return
|
return
|
||||||
|
|
||||||
operation_wise_completed_time = {}
|
operation_wise_completed_time = {}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"sub_operation",
|
"sub_operation",
|
||||||
|
"completed_qty",
|
||||||
"completed_time",
|
"completed_time",
|
||||||
"status",
|
"status"
|
||||||
"completed_qty"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -39,6 +39,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "completed_qty",
|
"fieldname": "completed_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Completed Qty",
|
"label": "Completed Qty",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
}
|
}
|
||||||
@@ -46,15 +47,16 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-27 13:09:57.090298",
|
"modified": "2025-08-20 21:44:43.941434",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Job Card Operation",
|
"name": "Job Card Operation",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
|
"row_format": "Dynamic",
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
|
"columns": 3,
|
||||||
"fieldname": "from_time",
|
"fieldname": "from_time",
|
||||||
"fieldtype": "Datetime",
|
"fieldtype": "Datetime",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@@ -58,17 +59,17 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "operation",
|
"fieldname": "operation",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Operation",
|
"in_list_view": 1,
|
||||||
|
"label": "Sub Operation",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Operation",
|
"options": "Operation"
|
||||||
"read_only": 1
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-08-04 15:47:11.748937",
|
"modified": "2025-08-20 21:49:59.084876",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Job Card Time Log",
|
"name": "Job Card Time Log",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import _
|
||||||
from frappe.tests import IntegrationTestCase
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
|
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")
|
bom_doc = setup_bom(item_code="_Testing Item", routing=routing_doc.name, currency="INR")
|
||||||
w1 = frappe.get_doc("Workstation", "_Test Workstation A")
|
w1 = frappe.get_doc("Workstation", "_Test Workstation A")
|
||||||
# resets values
|
# resets values
|
||||||
w1.hour_rate_rent = 300
|
for row in w1.workstation_costs:
|
||||||
w1.hour_rate_labour = 0
|
if row.operating_component == _("Rent"):
|
||||||
|
row.operating_cost = 300
|
||||||
|
break
|
||||||
|
|
||||||
w1.save()
|
w1.save()
|
||||||
bom_doc.update_cost()
|
bom_doc.update_cost()
|
||||||
bom_doc.reload()
|
bom_doc.reload()
|
||||||
self.assertEqual(w1.hour_rate, 300)
|
self.assertEqual(w1.hour_rate, 300)
|
||||||
self.assertEqual(bom_doc.operations[0].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()
|
w1.save()
|
||||||
# updating after setting new rates in workstations
|
# updating after setting new rates in workstations
|
||||||
bom_doc.update_cost()
|
bom_doc.update_cost()
|
||||||
@@ -102,8 +111,24 @@ def make_workstation(*args, **kwargs):
|
|||||||
workstation_name = args.workstation_name or args.workstation
|
workstation_name = args.workstation_name or args.workstation
|
||||||
if not frappe.db.exists("Workstation", workstation_name):
|
if not frappe.db.exists("Workstation", workstation_name):
|
||||||
doc = frappe.get_doc({"doctype": "Workstation", "workstation_name": workstation_name})
|
doc = frappe.get_doc({"doctype": "Workstation", "workstation_name": workstation_name})
|
||||||
doc.hour_rate_rent = args.get("hour_rate_rent")
|
if args.get("hour_rate_rent"):
|
||||||
doc.hour_rate_labour = args.get("hour_rate_labour")
|
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.workstation_type = args.get("workstation_type")
|
||||||
doc.insert()
|
doc.insert()
|
||||||
|
|
||||||
|
|||||||
@@ -27,11 +27,8 @@
|
|||||||
"column_break_etmc",
|
"column_break_etmc",
|
||||||
"off_status_image",
|
"off_status_image",
|
||||||
"over_heads",
|
"over_heads",
|
||||||
"hour_rate_electricity",
|
"section_break_auzm",
|
||||||
"hour_rate_consumable",
|
"workstation_costs",
|
||||||
"column_break_11",
|
|
||||||
"hour_rate_rent",
|
|
||||||
"hour_rate_labour",
|
|
||||||
"section_break_11",
|
"section_break_11",
|
||||||
"hour_rate",
|
"hour_rate",
|
||||||
"workstaion_description",
|
"workstaion_description",
|
||||||
@@ -68,50 +65,6 @@
|
|||||||
"label": "Operating Costs",
|
"label": "Operating Costs",
|
||||||
"oldfieldtype": "Section Break"
|
"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",
|
"description": "per hour",
|
||||||
"fieldname": "hour_rate",
|
"fieldname": "hour_rate",
|
||||||
@@ -252,6 +205,17 @@
|
|||||||
"fieldname": "disabled",
|
"fieldname": "disabled",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Disabled"
|
"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,
|
"hide_toolbar": 1,
|
||||||
@@ -259,7 +223,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"image_field": "on_status_image",
|
"image_field": "on_status_image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-07-13 16:02:13.615001",
|
"modified": "2025-08-19 12:07:05.374386",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Workstation",
|
"name": "Workstation",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _, bold
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import (
|
from frappe.utils import (
|
||||||
add_days,
|
add_days,
|
||||||
@@ -44,6 +44,7 @@ class Workstation(Document):
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from frappe.types import DF
|
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 (
|
from erpnext.manufacturing.doctype.workstation_working_hour.workstation_working_hour import (
|
||||||
WorkstationWorkingHour,
|
WorkstationWorkingHour,
|
||||||
)
|
)
|
||||||
@@ -52,10 +53,6 @@ class Workstation(Document):
|
|||||||
disabled: DF.Check
|
disabled: DF.Check
|
||||||
holiday_list: DF.Link | None
|
holiday_list: DF.Link | None
|
||||||
hour_rate: DF.Currency
|
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
|
off_status_image: DF.AttachImage | None
|
||||||
on_status_image: DF.AttachImage | None
|
on_status_image: DF.AttachImage | None
|
||||||
plant_floor: DF.Link | None
|
plant_floor: DF.Link | None
|
||||||
@@ -64,10 +61,26 @@ class Workstation(Document):
|
|||||||
total_working_hours: DF.Float
|
total_working_hours: DF.Float
|
||||||
warehouse: DF.Link | None
|
warehouse: DF.Link | None
|
||||||
working_hours: DF.Table[WorkstationWorkingHour]
|
working_hours: DF.Table[WorkstationWorkingHour]
|
||||||
|
workstation_costs: DF.Table[WorkstationCost]
|
||||||
workstation_name: DF.Data
|
workstation_name: DF.Data
|
||||||
workstation_type: DF.Link | None
|
workstation_type: DF.Link | None
|
||||||
# end: auto-generated types
|
# 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):
|
def before_save(self):
|
||||||
self.set_data_based_on_workstation_type()
|
self.set_data_based_on_workstation_type()
|
||||||
self.set_hour_rate()
|
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))
|
frappe.throw(_("Row #{0}: Start Time must be before End Time").format(row.idx))
|
||||||
|
|
||||||
def set_hour_rate(self):
|
def set_hour_rate(self):
|
||||||
self.hour_rate = (
|
self.hour_rate = 0.0
|
||||||
flt(self.hour_rate_labour)
|
for row in self.workstation_costs:
|
||||||
+ flt(self.hour_rate_electricity)
|
if row.operating_cost:
|
||||||
+ flt(self.hour_rate_consumable)
|
self.hour_rate += flt(row.operating_cost)
|
||||||
+ flt(self.hour_rate_rent)
|
|
||||||
)
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def set_data_based_on_workstation_type(self):
|
def set_data_based_on_workstation_type(self):
|
||||||
|
if self.workstation_costs:
|
||||||
|
return
|
||||||
|
|
||||||
if self.workstation_type:
|
if self.workstation_type:
|
||||||
fields = [
|
data = frappe.get_all(
|
||||||
"hour_rate_labour",
|
"Workstation Cost",
|
||||||
"hour_rate_electricity",
|
fields=["operating_component", "operating_cost", "idx"],
|
||||||
"hour_rate_consumable",
|
filters={"parent": self.workstation_type, "parenttype": "Workstation Type"},
|
||||||
"hour_rate_rent",
|
order_by="idx",
|
||||||
"hour_rate",
|
)
|
||||||
"description",
|
|
||||||
]
|
|
||||||
|
|
||||||
data = frappe.get_cached_value("Workstation Type", self.workstation_type, fields, as_dict=True)
|
for row in data:
|
||||||
|
self.append(
|
||||||
if not data:
|
"workstation_costs",
|
||||||
return
|
{
|
||||||
|
"operating_component": row.operating_component,
|
||||||
for field in fields:
|
"operating_cost": row.operating_cost,
|
||||||
if self.get(field):
|
"idx": row.idx,
|
||||||
continue
|
},
|
||||||
|
)
|
||||||
if value := data.get(field):
|
|
||||||
self.set(field, value)
|
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
self.validate_overlap_for_operation_timings()
|
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",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"workstation_type",
|
"workstation_type",
|
||||||
"over_heads",
|
"section_break_auzm",
|
||||||
"hour_rate_electricity",
|
"workstation_costs",
|
||||||
"hour_rate_consumable",
|
|
||||||
"column_break_5",
|
|
||||||
"hour_rate_rent",
|
|
||||||
"hour_rate_labour",
|
|
||||||
"section_break_8",
|
"section_break_8",
|
||||||
"hour_rate",
|
"hour_rate",
|
||||||
"description_tab",
|
"description_tab",
|
||||||
@@ -32,44 +28,6 @@
|
|||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"unique": 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",
|
"description": "per hour",
|
||||||
"fieldname": "hour_rate",
|
"fieldname": "hour_rate",
|
||||||
@@ -88,10 +46,6 @@
|
|||||||
"oldfieldtype": "Text",
|
"oldfieldtype": "Text",
|
||||||
"width": "300px"
|
"width": "300px"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "column_break_5",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "description_tab",
|
"fieldname": "description_tab",
|
||||||
@@ -101,11 +55,22 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "section_break_8",
|
"fieldname": "section_break_8",
|
||||||
"fieldtype": "Section Break"
|
"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",
|
"icon": "icon-wrench",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-27 13:11:00.946367",
|
"modified": "2025-08-19 12:06:56.683558",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Workstation Type",
|
"name": "Workstation Type",
|
||||||
@@ -125,9 +90,10 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
|
"row_format": "Dynamic",
|
||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "ASC",
|
"sort_order": "ASC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import _, bold
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import flt
|
from frappe.utils import flt
|
||||||
|
|
||||||
@@ -15,25 +16,38 @@ class WorkstationType(Document):
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from frappe.types import DF
|
from frappe.types import DF
|
||||||
|
|
||||||
|
from erpnext.manufacturing.doctype.workstation_cost.workstation_cost import WorkstationCost
|
||||||
|
|
||||||
description: DF.SmallText | None
|
description: DF.SmallText | None
|
||||||
hour_rate: DF.Currency
|
hour_rate: DF.Currency
|
||||||
hour_rate_consumable: DF.Currency
|
workstation_costs: DF.Table[WorkstationCost]
|
||||||
hour_rate_electricity: DF.Currency
|
|
||||||
hour_rate_labour: DF.Currency
|
|
||||||
hour_rate_rent: DF.Currency
|
|
||||||
workstation_type: DF.Data
|
workstation_type: DF.Data
|
||||||
# end: auto-generated types
|
# 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):
|
def before_save(self):
|
||||||
self.set_hour_rate()
|
self.set_hour_rate()
|
||||||
|
|
||||||
def set_hour_rate(self):
|
def set_hour_rate(self):
|
||||||
self.hour_rate = (
|
self.hour_rate = 0.0
|
||||||
flt(self.hour_rate_labour)
|
|
||||||
+ flt(self.hour_rate_electricity)
|
for row in self.workstation_costs:
|
||||||
+ flt(self.hour_rate_consumable)
|
if row.operating_cost:
|
||||||
+ flt(self.hour_rate_rent)
|
self.hour_rate += flt(row.operating_cost)
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_workstations(workstation_type):
|
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.v15_0.add_company_payment_gateway_account
|
||||||
erpnext.patches.v16_0.update_serial_no_reference_name
|
erpnext.patches.v16_0.update_serial_no_reference_name
|
||||||
erpnext.patches.v16_0.set_invoice_type_in_pos_settings
|
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.validate()
|
||||||
|
|
||||||
dunning.flags.ignore_validate_update_after_submit = True
|
dunning.flags.ignore_validate_update_after_submit = True
|
||||||
|
dunning.flags.ignore_links = True
|
||||||
dunning.save()
|
dunning.save()
|
||||||
|
|
||||||
# Reverse entries only if dunning is submitted and not resolved
|
# 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()
|
@frappe.whitelist()
|
||||||
def get_timesheet_detail_rate(timelog, currency):
|
def get_timesheet_detail_rate(timelog, currency):
|
||||||
timelog_detail = frappe.db.sql(
|
ts = frappe.qb.DocType("Timesheet")
|
||||||
f"""SELECT tsd.billing_amount as billing_amount,
|
ts_detail = frappe.qb.DocType("Timesheet Detail")
|
||||||
ts.currency as currency FROM `tabTimesheet Detail` tsd
|
|
||||||
INNER JOIN `tabTimesheet` ts ON ts.name=tsd.parent
|
timelog_detail = (
|
||||||
WHERE tsd.name = '{timelog}'""",
|
frappe.qb.from_(ts_detail)
|
||||||
as_dict=1,
|
.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]
|
)[0]
|
||||||
|
|
||||||
if timelog_detail.currency:
|
if timelog_detail.currency:
|
||||||
|
|||||||
@@ -5,61 +5,69 @@
|
|||||||
frappe.provide("erpnext.taxes");
|
frappe.provide("erpnext.taxes");
|
||||||
|
|
||||||
erpnext.accounts.taxes = {
|
erpnext.accounts.taxes = {
|
||||||
setup_tax_validations: function(doctype) {
|
setup_tax_validations: function (doctype) {
|
||||||
let me = this;
|
let me = this;
|
||||||
frappe.ui.form.on(doctype, {
|
frappe.ui.form.on(doctype, {
|
||||||
setup: function(frm) {
|
setup: function (frm) {
|
||||||
// set conditional display for rate column in taxes
|
// set conditional display for rate column in taxes
|
||||||
$(frm.wrapper).on('grid-row-render', function(e, grid_row) {
|
$(frm.wrapper).on("grid-row-render", function (e, grid_row) {
|
||||||
if(['Sales Taxes and Charges', 'Purchase Taxes and Charges'].includes(grid_row.doc.doctype)) {
|
if (
|
||||||
|
["Sales Taxes and Charges", "Purchase Taxes and Charges"].includes(
|
||||||
|
grid_row.doc.doctype
|
||||||
|
)
|
||||||
|
) {
|
||||||
me.set_conditional_mandatory_rate_or_amount(grid_row);
|
me.set_conditional_mandatory_rate_or_amount(grid_row);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onload: function(frm) {
|
onload: function (frm) {
|
||||||
if(frm.get_field("taxes")) {
|
if (frm.get_field("taxes")) {
|
||||||
frm.set_query("account_head", "taxes", function(doc) {
|
frm.set_query("account_head", "taxes", function (doc) {
|
||||||
if(frm.cscript.tax_table == "Sales Taxes and Charges") {
|
if (frm.cscript.tax_table == "Sales Taxes and Charges") {
|
||||||
var account_type = ["Tax", "Chargeable", "Expense Account"];
|
var account_type = ["Tax", "Chargeable", "Expense Account"];
|
||||||
} else {
|
} else {
|
||||||
var account_type = ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation"];
|
var account_type = [
|
||||||
|
"Tax",
|
||||||
|
"Chargeable",
|
||||||
|
"Income Account",
|
||||||
|
"Expenses Included In Valuation",
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
query: "erpnext.controllers.queries.tax_account_query",
|
query: "erpnext.controllers.queries.tax_account_query",
|
||||||
filters: {
|
filters: {
|
||||||
"account_type": account_type,
|
account_type: account_type,
|
||||||
"company": doc.company,
|
company: doc.company,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
frm.set_query("cost_center", "taxes", function(doc) {
|
frm.set_query("cost_center", "taxes", function (doc) {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
"company": doc.company,
|
company: doc.company,
|
||||||
"is_group": 0
|
is_group: 0,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
validate: function(frm) {
|
validate: function (frm) {
|
||||||
// neither is absolutely mandatory
|
// 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", "rate").reqd = 0;
|
||||||
frm.get_docfield("taxes", "tax_amount").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());
|
me.set_conditional_mandatory_rate_or_amount(frm.open_grid_row());
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
set_conditional_mandatory_rate_or_amount: function(grid_row) {
|
set_conditional_mandatory_rate_or_amount: function (grid_row) {
|
||||||
if(grid_row) {
|
if (grid_row) {
|
||||||
if(grid_row.doc.charge_type==="Actual") {
|
if (grid_row.doc.charge_type === "Actual") {
|
||||||
grid_row.toggle_editable("tax_amount", true);
|
grid_row.toggle_editable("tax_amount", true);
|
||||||
grid_row.toggle_reqd("tax_amount", true);
|
grid_row.toggle_reqd("tax_amount", true);
|
||||||
grid_row.toggle_editable("rate", false);
|
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 d = locals[cdt][cdn];
|
||||||
let msg = "";
|
let msg = "";
|
||||||
|
|
||||||
if (d.account_head && !d.description) {
|
if (d.account_head && !d.description) {
|
||||||
// set description from account head
|
// 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)) {
|
if (!d.charge_type && (d.row_id || d.rate || d.tax_amount)) {
|
||||||
msg = __("Please select Charge Type first");
|
msg = __("Please select Charge Type first");
|
||||||
d.row_id = "";
|
d.row_id = "";
|
||||||
d.rate = d.tax_amount = 0.0;
|
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) {
|
} else if (
|
||||||
msg = __("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'");
|
(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 = "";
|
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) {
|
if (d.idx == 1) {
|
||||||
msg = __("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row");
|
msg = __(
|
||||||
d.charge_type = '';
|
"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) {
|
} else if (!d.row_id) {
|
||||||
msg = __("Please specify a valid Row ID for row {0} in table {1}", [d.idx, __(d.doctype)]);
|
msg = __("Please specify a valid Row ID for row {0} in table {1}", [d.idx, __(d.doctype)]);
|
||||||
d.row_id = "";
|
d.row_id = "";
|
||||||
} else if (d.row_id && d.row_id >= d.idx) {
|
} 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 = "";
|
d.row_id = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,13 +128,12 @@ erpnext.accounts.taxes = {
|
|||||||
refresh_field("taxes");
|
refresh_field("taxes");
|
||||||
frappe.throw(msg);
|
frappe.throw(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setup_tax_filters: function(doctype) {
|
setup_tax_filters: function (doctype) {
|
||||||
let me = this;
|
let me = this;
|
||||||
frappe.ui.form.on(doctype, {
|
frappe.ui.form.on(doctype, {
|
||||||
account_head: function(frm, cdt, cdn) {
|
account_head: function (frm, cdt, cdn) {
|
||||||
let d = locals[cdt][cdn];
|
let d = locals[cdt][cdn];
|
||||||
|
|
||||||
if (d.docstatus == 1) {
|
if (d.docstatus == 1) {
|
||||||
@@ -120,150 +141,157 @@ erpnext.accounts.taxes = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!d.charge_type && d.account_head){
|
if (!d.charge_type && d.account_head) {
|
||||||
frappe.msgprint(__("Please select Charge Type first"));
|
frappe.msgprint(__("Please select Charge Type first"));
|
||||||
frappe.model.set_value(cdt, cdn, "account_head", "");
|
frappe.model.set_value(cdt, cdn, "account_head", "");
|
||||||
} else if (d.account_head) {
|
} else if (d.account_head) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
type:"GET",
|
type: "GET",
|
||||||
method: "erpnext.controllers.accounts_controller.get_tax_rate",
|
method: "erpnext.controllers.accounts_controller.get_tax_rate",
|
||||||
args: {"account_head":d.account_head},
|
args: { account_head: d.account_head },
|
||||||
callback: function(r) {
|
callback: function (r) {
|
||||||
if (d.charge_type!=="Actual") {
|
if (d.charge_type !== "Actual") {
|
||||||
frappe.model.set_value(cdt, cdn, "rate", r.message.tax_rate || 0);
|
frappe.model.set_value(cdt, cdn, "rate", r.message.tax_rate || 0);
|
||||||
}
|
}
|
||||||
frappe.model.set_value(cdt, cdn, "description", r.message.account_name);
|
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);
|
me.validate_taxes_and_charges(cdt, cdn);
|
||||||
},
|
},
|
||||||
rate: function(frm, cdt, cdn) {
|
rate: function (frm, cdt, cdn) {
|
||||||
me.validate_taxes_and_charges(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);
|
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);
|
me.validate_taxes_and_charges(cdt, cdn);
|
||||||
let open_form = frm.open_grid_row();
|
let open_form = frm.open_grid_row();
|
||||||
if(open_form) {
|
if (open_form) {
|
||||||
me.set_conditional_mandatory_rate_or_amount(open_form);
|
me.set_conditional_mandatory_rate_or_amount(open_form);
|
||||||
} else {
|
} else {
|
||||||
// apply in current row
|
// 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);
|
let tax = frappe.get_doc(cdt, cdn);
|
||||||
try {
|
try {
|
||||||
me.validate_taxes_and_charges(cdt, cdn);
|
me.validate_taxes_and_charges(cdt, cdn);
|
||||||
me.validate_inclusive_tax(tax, frm);
|
me.validate_inclusive_tax(tax, frm);
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
tax.included_in_print_rate = 0;
|
tax.included_in_print_rate = 0;
|
||||||
refresh_field("included_in_print_rate", tax.name, tax.parentfield);
|
refresh_field("included_in_print_rate", tax.name, tax.parentfield);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
validate_inclusive_tax: function(tax, frm) {
|
validate_inclusive_tax: function (tax, frm) {
|
||||||
this.frm = this.frm || frm;
|
this.frm = this.frm || frm;
|
||||||
let actual_type_error = function() {
|
let actual_type_error = function () {
|
||||||
var msg = __("Actual type tax cannot be included in Item rate in row {0}", [tax.idx])
|
var msg = __("Actual type tax cannot be included in Item rate in row {0}", [tax.idx]);
|
||||||
frappe.throw(msg);
|
frappe.throw(msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
let on_previous_row_error = function(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",
|
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])
|
tax.idx,
|
||||||
|
__(tax.doctype),
|
||||||
|
tax.charge_type,
|
||||||
|
row_range,
|
||||||
|
]);
|
||||||
frappe.throw(msg);
|
frappe.throw(msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
if(cint(tax.included_in_print_rate)) {
|
if (cint(tax.included_in_print_rate)) {
|
||||||
if(tax.charge_type == "Actual") {
|
if (tax.charge_type == "Actual") {
|
||||||
// inclusive tax cannot be of type Actual
|
// inclusive tax cannot be of type Actual
|
||||||
actual_type_error();
|
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)
|
!cint(this.frm.doc["taxes"][tax.row_id - 1].included_in_print_rate)
|
||||||
) {
|
) {
|
||||||
// referred row should also be an inclusive tax
|
// referred row should also be an inclusive tax
|
||||||
on_previous_row_error(tax.row_id);
|
on_previous_row_error(tax.row_id);
|
||||||
} else if (tax.charge_type == "On Previous Row Total" && this.frm) {
|
} 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),
|
var taxes_not_included = $.map(this.frm.doc["taxes"].slice(0, tax.row_id), function (t) {
|
||||||
function(t) { return cint(t.included_in_print_rate) ? null : t; });
|
return cint(t.included_in_print_rate) ? null : t;
|
||||||
if(taxes_not_included.length > 0) {
|
});
|
||||||
|
if (taxes_not_included.length > 0) {
|
||||||
// all rows above this tax should be inclusive
|
// all rows above this tax should be inclusive
|
||||||
on_previous_row_error(tax.row_id == 1 ? "1" : "1 - " + tax.row_id);
|
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"));
|
frappe.throw(__("Valuation type charges can not marked as Inclusive"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
erpnext.accounts.payment_triggers = {
|
erpnext.accounts.payment_triggers = {
|
||||||
setup: function(doctype) {
|
setup: function (doctype) {
|
||||||
frappe.ui.form.on(doctype, {
|
frappe.ui.form.on(doctype, {
|
||||||
allocate_advances_automatically(frm) {
|
allocate_advances_automatically(frm) {
|
||||||
frm.trigger('fetch_advances');
|
frm.trigger("fetch_advances");
|
||||||
},
|
},
|
||||||
|
|
||||||
only_include_allocated_payments(frm) {
|
only_include_allocated_payments(frm) {
|
||||||
frm.trigger('fetch_advances');
|
frm.trigger("fetch_advances");
|
||||||
},
|
},
|
||||||
|
|
||||||
fetch_advances(frm) {
|
fetch_advances(frm) {
|
||||||
if(frm.doc.allocate_advances_automatically) {
|
if (frm.doc.allocate_advances_automatically) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
method: "set_advances",
|
method: "set_advances",
|
||||||
callback: function(r, rt) {
|
callback: function (r, rt) {
|
||||||
refresh_field("advances");
|
refresh_field("advances");
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
erpnext.accounts.pos = {
|
erpnext.accounts.pos = {
|
||||||
setup: function(doctype) {
|
setup: function (doctype) {
|
||||||
frappe.ui.form.on(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];
|
var d = locals[cdt][cdn];
|
||||||
get_payment_mode_account(frm, d.mode_of_payment, function(account){
|
get_payment_mode_account(frm, d.mode_of_payment, function (account) {
|
||||||
frappe.model.set_value(cdt, cdn, 'account', account)
|
frappe.model.set_value(cdt, cdn, "account", account);
|
||||||
})
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
get_payment_mode_account: function(frm, mode_of_payment, callback) {
|
get_payment_mode_account: function (frm, mode_of_payment, callback) {
|
||||||
if(!frm.doc.company) {
|
if (!frm.doc.company) {
|
||||||
frappe.throw({message:__("Please select a Company first."), title: __("Mandatory")});
|
frappe.throw({ message: __("Please select a Company first."), title: __("Mandatory") });
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!mode_of_payment) {
|
if (!mode_of_payment) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return frappe.call({
|
return frappe.call({
|
||||||
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account",
|
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account",
|
||||||
args: {
|
args: {
|
||||||
"mode_of_payment": mode_of_payment,
|
mode_of_payment: mode_of_payment,
|
||||||
"company": frm.doc.company
|
company: frm.doc.company,
|
||||||
},
|
},
|
||||||
callback: function(r, rt) {
|
callback: function (r, rt) {
|
||||||
if(r.message) {
|
if (r.message) {
|
||||||
callback(r.message.account)
|
callback(r.message.account);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ frappe.provide("erpnext.buying");
|
|||||||
// cur_frm.add_fetch('project', 'cost_center', 'cost_center');
|
// cur_frm.add_fetch('project', 'cost_center', 'cost_center');
|
||||||
|
|
||||||
erpnext.buying = {
|
erpnext.buying = {
|
||||||
setup_buying_controller: function() {
|
setup_buying_controller: function () {
|
||||||
erpnext.buying.BuyingController = class BuyingController extends erpnext.TransactionController {
|
erpnext.buying.BuyingController = class BuyingController extends erpnext.TransactionController {
|
||||||
setup() {
|
setup() {
|
||||||
super.setup();
|
super.setup();
|
||||||
@@ -17,11 +17,11 @@ erpnext.buying = {
|
|||||||
this.setup_queries(doc, cdt, cdn);
|
this.setup_queries(doc, cdt, cdn);
|
||||||
super.onload();
|
super.onload();
|
||||||
|
|
||||||
this.frm.set_query('shipping_rule', function() {
|
this.frm.set_query("shipping_rule", function () {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
"shipping_rule_type": "Buying"
|
shipping_rule_type: "Buying",
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,29 +33,28 @@ erpnext.buying = {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.frm.doc.__islocal
|
if (
|
||||||
&& frappe.meta.has_field(this.frm.doc.doctype, "disable_rounded_total")) {
|
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);
|
var df = frappe.meta.get_docfield(this.frm.doc.doctype, "disable_rounded_total");
|
||||||
this.frm.set_value("disable_rounded_total", disable);
|
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
|
// 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", () => {
|
this.frm.set_query("shipping_address", () => {
|
||||||
if(this.frm.doc.customer) {
|
if (this.frm.doc.customer) {
|
||||||
return {
|
return {
|
||||||
query: 'frappe.contacts.doctype.address.address.address_query',
|
query: "frappe.contacts.doctype.address.address.address_query",
|
||||||
filters: { link_doctype: 'Customer', link_name: this.frm.doc.customer }
|
filters: { link_doctype: "Customer", link_name: this.frm.doc.customer },
|
||||||
};
|
};
|
||||||
} else
|
} else return erpnext.queries.company_address_query(this.frm.doc);
|
||||||
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", () => {
|
this.frm.set_query("dispatch_address", () => {
|
||||||
return erpnext.queries.address_query(this.frm.doc);
|
return erpnext.queries.address_query(this.frm.doc);
|
||||||
});
|
});
|
||||||
@@ -65,76 +64,77 @@ erpnext.buying = {
|
|||||||
setup_queries(doc, cdt, cdn) {
|
setup_queries(doc, cdt, cdn) {
|
||||||
var me = this;
|
var me = this;
|
||||||
|
|
||||||
if(this.frm.fields_dict.buying_price_list) {
|
if (this.frm.fields_dict.buying_price_list) {
|
||||||
this.frm.set_query("buying_price_list", function() {
|
this.frm.set_query("buying_price_list", function () {
|
||||||
return{
|
return {
|
||||||
filters: { 'buying': 1 }
|
filters: { buying: 1 },
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.frm.fields_dict.tc_name) {
|
if (this.frm.fields_dict.tc_name) {
|
||||||
this.frm.set_query("tc_name", function() {
|
this.frm.set_query("tc_name", function () {
|
||||||
return{
|
return {
|
||||||
filters: { 'buying': 1 }
|
filters: { buying: 1 },
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
me.frm.set_query('supplier', erpnext.queries.supplier);
|
me.frm.set_query("supplier", erpnext.queries.supplier);
|
||||||
me.frm.set_query('contact_person', erpnext.queries.contact_query);
|
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_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);
|
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) {
|
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) {
|
if (me.frm.doc.is_old_subcontracting_flow) {
|
||||||
filters["is_sub_contracted_item"] = 1;
|
filters["is_sub_contracted_item"] = 1;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
filters["is_stock_item"] = 0;
|
filters["is_stock_item"] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return{
|
return {
|
||||||
query: "erpnext.controllers.queries.item_query",
|
query: "erpnext.controllers.queries.item_query",
|
||||||
filters: filters
|
filters: filters,
|
||||||
}
|
};
|
||||||
}
|
} else {
|
||||||
else {
|
return {
|
||||||
return{
|
|
||||||
query: "erpnext.controllers.queries.item_query",
|
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];
|
const row = locals[cdt][cdn];
|
||||||
return {
|
return {
|
||||||
query: "erpnext.controllers.queries.item_manufacturer_query",
|
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')) {
|
if (this.frm.fields_dict["items"].grid.get_field("item_code")) {
|
||||||
this.frm.set_query("item_tax_template", "items", function(doc, cdt, cdn) {
|
this.frm.set_query("item_tax_template", "items", function (doc, cdt, cdn) {
|
||||||
return me.set_query_for_item_tax_template(doc, cdt, cdn)
|
return me.set_query_for_item_tax_template(doc, cdt, cdn);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh(doc) {
|
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.toggle_display(
|
||||||
(this.frm.doc.supplier_name && this.frm.doc.supplier_name!==this.frm.doc.supplier));
|
"supplier_name",
|
||||||
|
this.frm.doc.supplier_name && this.frm.doc.supplier_name !== this.frm.doc.supplier
|
||||||
|
);
|
||||||
|
|
||||||
if(this.frm.doc.docstatus==0 &&
|
if (
|
||||||
(this.frm.doctype==="Purchase Order" || this.frm.doctype==="Material Request")) {
|
this.frm.doc.docstatus == 0 &&
|
||||||
|
(this.frm.doctype === "Purchase Order" || this.frm.doctype === "Material Request")
|
||||||
|
) {
|
||||||
this.set_from_product_bundle();
|
this.set_from_product_bundle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,45 +143,53 @@ erpnext.buying = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggle_subcontracting_fields() {
|
toggle_subcontracting_fields() {
|
||||||
if (['Purchase Receipt', 'Purchase Invoice'].includes(this.frm.doc.doctype)) {
|
if (["Purchase Receipt", "Purchase Invoice"].includes(this.frm.doc.doctype)) {
|
||||||
this.frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty',
|
this.frm.fields_dict.supplied_items.grid.update_docfield_property(
|
||||||
'read_only', this.frm.doc.__onload && this.frm.doc.__onload.backflush_based_on === 'BOM');
|
"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_add_rows", 1);
|
||||||
this.frm.set_df_property('supplied_items', 'cannot_delete_rows', 1);
|
this.frm.set_df_property("supplied_items", "cannot_delete_rows", 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
supplier() {
|
supplier() {
|
||||||
var me = this;
|
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();
|
me.apply_price_list();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
company(){
|
company() {
|
||||||
if(!frappe.meta.has_field(this.frm.doc.doctype, "billing_address")) return;
|
if (!frappe.meta.has_field(this.frm.doc.doctype, "billing_address")) return;
|
||||||
|
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.setup.doctype.company.company.get_billing_shipping_address",
|
method: "erpnext.setup.doctype.company.company.get_billing_shipping_address",
|
||||||
args: {
|
args: {
|
||||||
name: this.frm.doc.company,
|
name: this.frm.doc.company,
|
||||||
billing_address:this.frm.doc.billing_address,
|
billing_address: this.frm.doc.billing_address,
|
||||||
shipping_address: this.frm.doc.shipping_address
|
shipping_address: this.frm.doc.shipping_address,
|
||||||
},
|
},
|
||||||
callback: (r) => {
|
callback: (r) => {
|
||||||
this.frm.set_value("billing_address", r.message.primary_address || "");
|
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 || "");
|
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() {
|
supplier_address() {
|
||||||
erpnext.utils.get_address_display(this.frm);
|
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() {
|
buying_price_list() {
|
||||||
@@ -201,24 +209,33 @@ erpnext.buying = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
qty(doc, cdt, cdn) {
|
qty(doc, cdt, cdn) {
|
||||||
if ((doc.doctype == "Purchase Receipt") || (doc.doctype == "Purchase Invoice" && doc.update_stock)) {
|
if (
|
||||||
this.calculate_received_qty(doc, cdt, cdn)
|
doc.doctype == "Purchase Receipt" ||
|
||||||
|
(doc.doctype == "Purchase Invoice" && doc.update_stock)
|
||||||
|
) {
|
||||||
|
this.calculate_received_qty(doc, cdt, cdn);
|
||||||
}
|
}
|
||||||
super.qty(doc, cdt, cdn);
|
super.qty(doc, cdt, cdn);
|
||||||
}
|
}
|
||||||
|
|
||||||
rejected_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);
|
var item = frappe.get_doc(cdt, cdn);
|
||||||
frappe.model.round_floats_in(item, ["qty", "rejected_qty"]);
|
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_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_qty", received_qty);
|
||||||
frappe.model.set_value(cdt, cdn, "received_stock_qty", received_stock_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);
|
super.batch_no(doc, cdt, cdn);
|
||||||
}
|
}
|
||||||
|
|
||||||
validate_negative_quantity(cdt, cdn, item, fieldnames){
|
validate_negative_quantity(cdt, cdn, item, fieldnames) {
|
||||||
if(!item || !fieldnames) { return }
|
if (!item || !fieldnames) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var is_negative_qty = false;
|
var is_negative_qty = false;
|
||||||
for(var i = 0; i<fieldnames.length; i++) {
|
for (var i = 0; i < fieldnames.length; i++) {
|
||||||
if(item[fieldnames[i]] < 0){
|
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]));
|
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;
|
is_negative_qty = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return is_negative_qty
|
return is_negative_qty;
|
||||||
}
|
}
|
||||||
|
|
||||||
warehouse(doc, cdt, cdn) {
|
warehouse(doc, cdt, cdn) {
|
||||||
var item = frappe.get_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({
|
return this.frm.call({
|
||||||
method: "erpnext.stock.get_item_details.get_bin_details",
|
method: "erpnext.stock.get_item_details.get_bin_details",
|
||||||
child: item,
|
child: item,
|
||||||
@@ -253,22 +278,21 @@ erpnext.buying = {
|
|||||||
item_code: item.item_code,
|
item_code: item.item_code,
|
||||||
warehouse: item.warehouse,
|
warehouse: item.warehouse,
|
||||||
company: doc.company,
|
company: doc.company,
|
||||||
include_child_warehouses: true
|
include_child_warehouses: true,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
project(doc, cdt, cdn) {
|
project(doc, cdt, cdn) {
|
||||||
var item = frappe.get_doc(cdt, cdn);
|
var item = frappe.get_doc(cdt, cdn);
|
||||||
if(item.project) {
|
if (item.project) {
|
||||||
$.each(this.frm.doc["items"] || [],
|
$.each(this.frm.doc["items"] || [], function (i, other_item) {
|
||||||
function(i, other_item) {
|
if (!other_item.project) {
|
||||||
if(!other_item.project) {
|
other_item.project = item.project;
|
||||||
other_item.project = item.project;
|
refresh_field("project", other_item.name, other_item.parentfield);
|
||||||
refresh_field("project", other_item.name, other_item.parentfield);
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +305,7 @@ erpnext.buying = {
|
|||||||
|
|
||||||
category(doc, cdt, cdn) {
|
category(doc, cdt, cdn) {
|
||||||
// should be the category field of tax table
|
// should be the category field of tax table
|
||||||
if(cdt != doc.doctype) {
|
if (cdt != doc.doctype) {
|
||||||
this.calculate_taxes_and_totals();
|
this.calculate_taxes_and_totals();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,26 +315,42 @@ erpnext.buying = {
|
|||||||
|
|
||||||
set_from_product_bundle() {
|
set_from_product_bundle() {
|
||||||
var me = this;
|
var me = this;
|
||||||
this.frm.add_custom_button(__("Product Bundle"), function() {
|
this.frm.add_custom_button(
|
||||||
erpnext.buying.get_items_from_product_bundle(me.frm);
|
__("Product Bundle"),
|
||||||
}, __("Get Items From"));
|
function () {
|
||||||
|
erpnext.buying.get_items_from_product_bundle(me.frm);
|
||||||
|
},
|
||||||
|
__("Get Items From")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
shipping_address(){
|
shipping_address() {
|
||||||
var me = this;
|
var me = this;
|
||||||
erpnext.utils.get_address_display(this.frm, "shipping_address",
|
erpnext.utils.get_address_display(
|
||||||
"shipping_address_display", true);
|
this.frm,
|
||||||
|
"shipping_address",
|
||||||
|
"shipping_address_display",
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch_address(){
|
dispatch_address() {
|
||||||
var me = this;
|
var me = this;
|
||||||
erpnext.utils.get_address_display(this.frm, "dispatch_address",
|
erpnext.utils.get_address_display(
|
||||||
"dispatch_address_display", true);
|
this.frm,
|
||||||
|
"dispatch_address",
|
||||||
|
"dispatch_address_display",
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
billing_address() {
|
billing_address() {
|
||||||
erpnext.utils.get_address_display(this.frm, "billing_address",
|
erpnext.utils.get_address_display(
|
||||||
"billing_address_display", true);
|
this.frm,
|
||||||
|
"billing_address",
|
||||||
|
"billing_address_display",
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
tc_name() {
|
tc_name() {
|
||||||
@@ -320,37 +360,43 @@ erpnext.buying = {
|
|||||||
update_auto_repeat_reference(doc) {
|
update_auto_repeat_reference(doc) {
|
||||||
if (doc.auto_repeat) {
|
if (doc.auto_repeat) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method:"frappe.automation.doctype.auto_repeat.auto_repeat.update_reference",
|
method: "frappe.automation.doctype.auto_repeat.auto_repeat.update_reference",
|
||||||
args:{
|
args: {
|
||||||
docname: doc.auto_repeat,
|
docname: doc.auto_repeat,
|
||||||
reference:doc.name
|
reference: doc.name,
|
||||||
},
|
},
|
||||||
callback: function(r){
|
callback: function (r) {
|
||||||
if (r.message=="success") {
|
if (r.message == "success") {
|
||||||
frappe.show_alert({message:__("Auto repeat document updated"), indicator:'green'});
|
frappe.show_alert({
|
||||||
|
message: __("Auto repeat document updated"),
|
||||||
|
indicator: "green",
|
||||||
|
});
|
||||||
} else {
|
} 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) {
|
manufacturer(doc, cdt, cdn) {
|
||||||
const row = locals[cdt][cdn];
|
const row = locals[cdt][cdn];
|
||||||
|
|
||||||
if(row.manufacturer) {
|
if (row.manufacturer) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.stock.doctype.item_manufacturer.item_manufacturer.get_item_manufacturer_part_no",
|
method: "erpnext.stock.doctype.item_manufacturer.item_manufacturer.get_item_manufacturer_part_no",
|
||||||
args: {
|
args: {
|
||||||
'item_code': row.item_code,
|
item_code: row.item_code,
|
||||||
'manufacturer': row.manufacturer
|
manufacturer: row.manufacturer,
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function (r) {
|
||||||
if (r.message) {
|
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];
|
const row = locals[cdt][cdn];
|
||||||
|
|
||||||
if (row.manufacturer_part_no) {
|
if (row.manufacturer_part_no) {
|
||||||
frappe.model.get_value('Item Manufacturer',
|
frappe.model.get_value(
|
||||||
|
"Item Manufacturer",
|
||||||
{
|
{
|
||||||
'item_code': row.item_code,
|
item_code: row.item_code,
|
||||||
'manufacturer': row.manufacturer,
|
manufacturer: row.manufacturer,
|
||||||
'manufacturer_part_no': row.manufacturer_part_no
|
manufacturer_part_no: row.manufacturer_part_no,
|
||||||
},
|
},
|
||||||
'name',
|
"name",
|
||||||
function(data) {
|
function (data) {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
let msg = {
|
let msg = {
|
||||||
message: __("Manufacturer Part Number <b>{0}</b> is invalid", [row.manufacturer_part_no]),
|
message: __("Manufacturer Part Number <b>{0}</b> is invalid", [
|
||||||
title: __("Invalid Part Number")
|
row.manufacturer_part_no,
|
||||||
}
|
]),
|
||||||
|
title: __("Invalid Part Number"),
|
||||||
|
};
|
||||||
frappe.throw(msg);
|
frappe.throw(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -384,40 +433,42 @@ erpnext.buying = {
|
|||||||
let me = this;
|
let me = this;
|
||||||
let fields = ["has_batch_no", "has_serial_no"];
|
let fields = ["has_batch_no", "has_serial_no"];
|
||||||
|
|
||||||
frappe.db.get_value("Item", item.item_code, fields)
|
frappe.db.get_value("Item", item.item_code, fields).then((r) => {
|
||||||
.then((r) => {
|
if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) {
|
||||||
if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) {
|
fields.forEach((field) => {
|
||||||
fields.forEach((field) => {
|
item[field] = r.message[field];
|
||||||
item[field] = r.message[field];
|
});
|
||||||
});
|
|
||||||
|
|
||||||
item.type_of_transaction = item.qty > 0 ? "Inward" : "Outward";
|
item.type_of_transaction = item.qty > 0 ? "Inward" : "Outward";
|
||||||
item.is_rejected = false;
|
item.is_rejected = false;
|
||||||
|
|
||||||
new erpnext.SerialBatchPackageSelector(
|
new erpnext.SerialBatchPackageSelector(me.frm, item, (r) => {
|
||||||
me.frm, item, (r) => {
|
if (r) {
|
||||||
if (r) {
|
let qty = Math.abs(r.total_qty);
|
||||||
let qty = Math.abs(r.total_qty);
|
if (doc.is_return) {
|
||||||
if (doc.is_return) {
|
qty = qty * -1;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
}
|
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) {
|
add_serial_batch_for_rejected_qty(doc, cdt, cdn) {
|
||||||
@@ -425,142 +476,147 @@ erpnext.buying = {
|
|||||||
let me = this;
|
let me = this;
|
||||||
let fields = ["has_batch_no", "has_serial_no"];
|
let fields = ["has_batch_no", "has_serial_no"];
|
||||||
|
|
||||||
frappe.db.get_value("Item", item.item_code, fields)
|
frappe.db.get_value("Item", item.item_code, fields).then((r) => {
|
||||||
.then((r) => {
|
if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) {
|
||||||
if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) {
|
fields.forEach((field) => {
|
||||||
fields.forEach((field) => {
|
item[field] = r.message[field];
|
||||||
item[field] = r.message[field];
|
});
|
||||||
});
|
|
||||||
|
|
||||||
item.type_of_transaction = !doc.is_return > 0 ? "Inward" : "Outward";
|
item.type_of_transaction = !doc.is_return > 0 ? "Inward" : "Outward";
|
||||||
item.is_rejected = true;
|
item.is_rejected = true;
|
||||||
|
|
||||||
new erpnext.SerialBatchPackageSelector(
|
new erpnext.SerialBatchPackageSelector(me.frm, item, (r) => {
|
||||||
me.frm, item, (r) => {
|
if (r) {
|
||||||
if (r) {
|
let qty = Math.abs(r.total_qty);
|
||||||
let qty = Math.abs(r.total_qty);
|
if (doc.is_return) {
|
||||||
if (doc.is_return) {
|
qty = qty * -1;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
}
|
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({
|
frappe.call({
|
||||||
method: "erpnext.buying.utils.get_linked_material_requests",
|
method: "erpnext.buying.utils.get_linked_material_requests",
|
||||||
args:{
|
args: {
|
||||||
items: frm.doc.items.map((item) => item.item_code)
|
items: frm.doc.items.map((item) => item.item_code),
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function (r) {
|
||||||
if (!r.message || r.message.length == 0) {
|
if (!r.message || r.message.length == 0) {
|
||||||
frappe.throw({
|
frappe.throw({
|
||||||
message: __("No pending Material Requests found to link for the given items."),
|
message: __("No pending Material Requests found to link for the given items."),
|
||||||
title: __("Note")
|
title: __("Note"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var item_length = frm.doc.items.length;
|
var item_length = frm.doc.items.length;
|
||||||
for (let item of frm.doc.items) {
|
for (let item of frm.doc.items) {
|
||||||
var qty = item.qty;
|
var qty = item.qty;
|
||||||
(r.message[0] || []).forEach(function(d) {
|
(r.message[0] || []).forEach(function (d) {
|
||||||
if (d.qty > 0 && qty > 0 && item.item_code == d.item_code && !item.material_request_item)
|
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 = d.mr_name;
|
||||||
item.material_request_item = d.mr_item;
|
item.material_request_item = d.mr_item;
|
||||||
var my_qty = Math.min(qty, d.qty);
|
var my_qty = Math.min(qty, d.qty);
|
||||||
qty = qty - my_qty;
|
qty = qty - my_qty;
|
||||||
d.qty = d.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;
|
item.qty = my_qty;
|
||||||
|
|
||||||
frappe.msgprint("Assigning " + d.mr_name + " to " + d.item_code + " (row " + item.idx + ")");
|
frappe.msgprint(
|
||||||
if (qty > 0)
|
"Assigning " + d.mr_name + " to " + d.item_code + " (row " + item.idx + ")"
|
||||||
{
|
);
|
||||||
|
if (qty > 0) {
|
||||||
frappe.msgprint("Splitting " + qty + " units of " + d.item_code);
|
frappe.msgprint("Splitting " + qty + " units of " + d.item_code);
|
||||||
var newrow = frappe.model.add_child(frm.doc, item.doctype, "items");
|
var newrow = frappe.model.add_child(frm.doc, item.doctype, "items");
|
||||||
item_length++;
|
item_length++;
|
||||||
|
|
||||||
for (var key in item)
|
for (var key in item) {
|
||||||
{
|
|
||||||
newrow[key] = item[key];
|
newrow[key] = item[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
newrow.idx = item_length;
|
newrow.idx = item_length;
|
||||||
newrow["stock_qty"] = newrow.conversion_factor*qty;
|
newrow["stock_qty"] = newrow.conversion_factor * qty;
|
||||||
newrow["qty"] = qty;
|
newrow["qty"] = qty;
|
||||||
|
|
||||||
newrow["material_request"] = "";
|
newrow["material_request"] = "";
|
||||||
newrow["material_request_item"] = "";
|
newrow["material_request_item"] = "";
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
refresh_field("items");
|
refresh_field("items");
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
erpnext.buying.get_default_bom = function(frm) {
|
erpnext.buying.get_default_bom = function (frm) {
|
||||||
$.each(frm.doc["items"] || [], function(i, d) {
|
$.each(frm.doc["items"] || [], function (i, d) {
|
||||||
if (d.item_code && d.bom === "") {
|
if (d.item_code && d.bom === "") {
|
||||||
return frappe.call({
|
return frappe.call({
|
||||||
type: "GET",
|
type: "GET",
|
||||||
method: "erpnext.stock.get_item_details.get_default_bom",
|
method: "erpnext.stock.get_item_details.get_default_bom",
|
||||||
args: {
|
args: {
|
||||||
"item_code": d.item_code,
|
item_code: d.item_code,
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function (r) {
|
||||||
if(r) {
|
if (r) {
|
||||||
frappe.model.set_value(d.doctype, d.name, "bom", r.message);
|
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({
|
var dialog = new frappe.ui.Dialog({
|
||||||
title: __("Get Items from Product Bundle"),
|
title: __("Get Items from Product Bundle"),
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
"fieldtype": "Link",
|
fieldtype: "Link",
|
||||||
"label": __("Product Bundle"),
|
label: __("Product Bundle"),
|
||||||
"fieldname": "product_bundle",
|
fieldname: "product_bundle",
|
||||||
"options":"Product Bundle",
|
options: "Product Bundle",
|
||||||
"reqd": 1
|
reqd: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldtype": "Currency",
|
fieldtype: "Currency",
|
||||||
"label": __("Quantity"),
|
label: __("Quantity"),
|
||||||
"fieldname": "quantity",
|
fieldname: "quantity",
|
||||||
"reqd": 1,
|
reqd: 1,
|
||||||
"default": 1
|
default: 1,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
primary_action_label: 'Get Items',
|
primary_action_label: "Get Items",
|
||||||
primary_action(args){
|
primary_action(args) {
|
||||||
if(!args) return;
|
if (!args) return;
|
||||||
dialog.hide();
|
dialog.hide();
|
||||||
return frappe.call({
|
return frappe.call({
|
||||||
type: "GET",
|
type: "GET",
|
||||||
@@ -581,44 +637,44 @@ erpnext.buying.get_items_from_product_bundle = function(frm) {
|
|||||||
is_subcontracted: frm.doc.is_subcontracted,
|
is_subcontracted: frm.doc.is_subcontracted,
|
||||||
transaction_date: frm.doc.transaction_date || frm.doc.posting_date,
|
transaction_date: frm.doc.transaction_date || frm.doc.posting_date,
|
||||||
ignore_pricing_rule: frm.doc.ignore_pricing_rule,
|
ignore_pricing_rule: frm.doc.ignore_pricing_rule,
|
||||||
doctype: frm.doc.doctype
|
doctype: frm.doc.doctype,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
freeze: true,
|
freeze: true,
|
||||||
callback: function(r) {
|
callback: function (r) {
|
||||||
const first_row_is_empty = function(child_table){
|
const first_row_is_empty = function (child_table) {
|
||||||
if($.isArray(child_table) && child_table.length > 0) {
|
if ($.isArray(child_table) && child_table.length > 0) {
|
||||||
return !child_table[0].item_code;
|
return !child_table[0].item_code;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const remove_empty_first_row = function(frm){
|
const remove_empty_first_row = function (frm) {
|
||||||
if (first_row_is_empty(frm.doc.items)){
|
if (first_row_is_empty(frm.doc.items)) {
|
||||||
frm.doc.items = frm.doc.items.splice(1);
|
frm.doc.items = frm.doc.items.splice(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!r.exc && r.message) {
|
if (!r.exc && r.message) {
|
||||||
remove_empty_first_row(frm);
|
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 d = frm.add_child("items");
|
||||||
var item = r.message[i];
|
var item = r.message[i];
|
||||||
for (var key in item) {
|
for (var key in item) {
|
||||||
if (!is_null(item[key]) && key !== "doctype") {
|
if (!is_null(item[key]) && key !== "doctype") {
|
||||||
d[key] = item[key];
|
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.script_manager.trigger("price_list_rate", d.doctype, d.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
frm.refresh_field("items");
|
frm.refresh_field("items");
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
dialog.show();
|
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];
|
let row = locals[cdt][cdn];
|
||||||
if (row.barcode) {
|
if (row.barcode) {
|
||||||
erpnext.stock.utils.set_item_details_using_barcode(this.frm, row, (r) => {
|
erpnext.stock.utils.set_item_details_using_barcode(this.frm, row, (r) => {
|
||||||
frappe.model.set_value(cdt, cdn, {
|
frappe.model.set_value(cdt, cdn, {
|
||||||
"item_code": r.message.item_code,
|
item_code: r.message.item_code,
|
||||||
"qty": 1,
|
qty: 1,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -25,74 +25,81 @@ erpnext.stock.StockController = class StockController extends frappe.ui.form.Con
|
|||||||
|
|
||||||
setup_warehouse_query() {
|
setup_warehouse_query() {
|
||||||
var me = this;
|
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);
|
return erpnext.queries.warehouse(me.frm.doc);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_posting_date_time_check() {
|
setup_posting_date_time_check() {
|
||||||
// make posting date default and read only unless explictly checked
|
// 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) {
|
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) {
|
if (frm.doc.docstatus == 0 && frm.doc.set_posting_time) {
|
||||||
frm.set_df_property('posting_date', 'read_only', 0);
|
frm.set_df_property("posting_date", "read_only", 0);
|
||||||
frm.set_df_property('posting_time', 'read_only', 0);
|
frm.set_df_property("posting_time", "read_only", 0);
|
||||||
} else {
|
} else {
|
||||||
frm.set_df_property('posting_date', 'read_only', 1);
|
frm.set_df_property("posting_date", "read_only", 1);
|
||||||
frm.set_df_property('posting_time', '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
|
// set default posting date / time
|
||||||
if(frm.doc.docstatus==0) {
|
if (frm.doc.docstatus == 0) {
|
||||||
if(!frm.doc.posting_date) {
|
if (!frm.doc.posting_date) {
|
||||||
frm.set_value('posting_date', frappe.datetime.nowdate());
|
frm.set_value("posting_date", frappe.datetime.nowdate());
|
||||||
}
|
}
|
||||||
if(!frm.doc.posting_time) {
|
if (!frm.doc.posting_time) {
|
||||||
frm.set_value('posting_time', frappe.datetime.now_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() {
|
show_stock_ledger() {
|
||||||
var me = this;
|
var me = this;
|
||||||
if(this.frm.doc.docstatus > 0) {
|
if (this.frm.doc.docstatus > 0) {
|
||||||
cur_frm.add_custom_button(__("Stock Ledger"), function() {
|
cur_frm.add_custom_button(
|
||||||
frappe.route_options = {
|
__("Stock Ledger"),
|
||||||
voucher_no: me.frm.doc.name,
|
function () {
|
||||||
from_date: me.frm.doc.posting_date,
|
frappe.route_options = {
|
||||||
to_date: moment(me.frm.doc.modified).format('YYYY-MM-DD'),
|
voucher_no: me.frm.doc.name,
|
||||||
company: me.frm.doc.company,
|
from_date: me.frm.doc.posting_date,
|
||||||
show_cancelled_entries: me.frm.doc.docstatus === 2,
|
to_date: moment(me.frm.doc.modified).format("YYYY-MM-DD"),
|
||||||
ignore_prepared_report: true
|
company: me.frm.doc.company,
|
||||||
};
|
show_cancelled_entries: me.frm.doc.docstatus === 2,
|
||||||
frappe.set_route("query-report", "Stock Ledger");
|
ignore_prepared_report: true,
|
||||||
}, __("View"));
|
};
|
||||||
|
frappe.set_route("query-report", "Stock Ledger");
|
||||||
|
},
|
||||||
|
__("View")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
show_general_ledger() {
|
show_general_ledger() {
|
||||||
let me = this;
|
let me = this;
|
||||||
if(this.frm.doc.docstatus > 0) {
|
if (this.frm.doc.docstatus > 0) {
|
||||||
cur_frm.add_custom_button(__('Accounting Ledger'), function() {
|
cur_frm.add_custom_button(
|
||||||
frappe.route_options = {
|
__("Accounting Ledger"),
|
||||||
voucher_no: me.frm.doc.name,
|
function () {
|
||||||
from_date: me.frm.doc.posting_date,
|
frappe.route_options = {
|
||||||
to_date: moment(me.frm.doc.modified).format('YYYY-MM-DD'),
|
voucher_no: me.frm.doc.name,
|
||||||
company: me.frm.doc.company,
|
from_date: me.frm.doc.posting_date,
|
||||||
categorize_by: "Categorize by Voucher (Consolidated)",
|
to_date: moment(me.frm.doc.modified).format("YYYY-MM-DD"),
|
||||||
show_cancelled_entries: me.frm.doc.docstatus === 2,
|
company: me.frm.doc.company,
|
||||||
ignore_prepared_report: true
|
categorize_by: "Categorize by Voucher (Consolidated)",
|
||||||
};
|
show_cancelled_entries: me.frm.doc.docstatus === 2,
|
||||||
frappe.set_route("query-report", "General Ledger");
|
ignore_prepared_report: true,
|
||||||
}, __("View"));
|
};
|
||||||
|
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()
|
wo.reload()
|
||||||
so.reload()
|
so.reload()
|
||||||
self.assertEqual(so.items[0].work_order_qty, wo.produced_qty)
|
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):
|
def test_sales_order_with_shipping_rule(self):
|
||||||
from erpnext.accounts.doctype.shipping_rule.test_shipping_rule import create_shipping_rule
|
from erpnext.accounts.doctype.shipping_rule.test_shipping_rule import create_shipping_rule
|
||||||
|
|||||||
@@ -450,7 +450,6 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render_payment_section() {
|
render_payment_section() {
|
||||||
this.grand_total_to_default_mop();
|
|
||||||
this.render_payment_mode_dom();
|
this.render_payment_mode_dom();
|
||||||
this.make_invoice_field_dialog();
|
this.make_invoice_field_dialog();
|
||||||
this.update_totals_section();
|
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() {
|
render_payment_mode_dom() {
|
||||||
const doc = this.events.get_frm().doc;
|
const doc = this.events.get_frm().doc;
|
||||||
const payments = doc.payments;
|
const payments = doc.payments;
|
||||||
|
|||||||
@@ -294,6 +294,10 @@ def install(country=None):
|
|||||||
{"doctype": "Market Segment", "market_segment": _("Upper Income")},
|
{"doctype": "Market Segment", "market_segment": _("Upper Income")},
|
||||||
# Warehouse Type
|
# Warehouse Type
|
||||||
{"doctype": "Warehouse Type", "name": "Transit"},
|
{"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 (
|
for doctype, title_field, filename in (
|
||||||
@@ -481,14 +485,19 @@ def install_defaults(args=None): # nosemgrep
|
|||||||
create_bank_account(args)
|
create_bank_account(args)
|
||||||
|
|
||||||
|
|
||||||
def set_global_defaults(args):
|
def set_global_defaults(kwargs):
|
||||||
global_defaults = frappe.get_doc("Global Defaults", "Global Defaults")
|
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(
|
global_defaults.update(
|
||||||
{
|
{
|
||||||
"default_currency": args.get("currency"),
|
"default_currency": kwargs.get("currency"),
|
||||||
"default_company": args.get("company_name"),
|
"default_company": company,
|
||||||
"country": args.get("country"),
|
"country": kwargs.get("country"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -667,13 +667,13 @@ def prepare_data_for_internal_transfer():
|
|||||||
company = "_Test Company with perpetual inventory"
|
company = "_Test Company with perpetual inventory"
|
||||||
|
|
||||||
customer = create_internal_customer(
|
customer = create_internal_customer(
|
||||||
"_Test Internal Customer 3",
|
"_Test Internal Customer 2",
|
||||||
company,
|
company,
|
||||||
company,
|
company,
|
||||||
)
|
)
|
||||||
|
|
||||||
supplier = create_internal_supplier(
|
supplier = create_internal_supplier(
|
||||||
"_Test Internal Supplier 3",
|
"_Test Internal Supplier 2",
|
||||||
company,
|
company,
|
||||||
company,
|
company,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import frappe
|
|||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
from frappe import _, msgprint
|
from frappe import _, msgprint
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
from frappe.query_builder import Order
|
||||||
from frappe.query_builder.functions import Sum
|
from frappe.query_builder.functions import Sum
|
||||||
from frappe.utils import cint, cstr, flt, get_link_to_form, getdate, new_line_sep, nowdate
|
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.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
def get_material_requests_based_on_supplier(doctype, txt, searchfield, start, page_len, filters):
|
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 = filters.get("supplier")
|
||||||
supplier_items = get_items_based_on_default_supplier(supplier)
|
supplier_items = get_items_based_on_default_supplier(supplier)
|
||||||
|
|
||||||
if not supplier_items:
|
if not supplier_items:
|
||||||
frappe.throw(_("{0} is not the default supplier for any items.").format(supplier))
|
frappe.throw(_("{0} is not the default supplier for any items.").format(supplier))
|
||||||
|
|
||||||
material_requests = frappe.db.sql(
|
mr = frappe.qb.DocType("Material Request")
|
||||||
"""select distinct mr.name, transaction_date,company
|
mr_item = frappe.qb.DocType("Material Request Item")
|
||||||
from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
|
|
||||||
where mr.name = mr_item.parent
|
query = (
|
||||||
and mr_item.item_code in ({})
|
frappe.qb.from_(mr)
|
||||||
and mr.material_request_type = 'Purchase'
|
.from_(mr_item)
|
||||||
and mr.per_ordered < 99.99
|
.select(mr.name)
|
||||||
and mr.docstatus = 1
|
.distinct()
|
||||||
and mr.status != 'Stopped'
|
.select(mr.transaction_date, mr.company)
|
||||||
and mr.company = %s
|
.where(
|
||||||
{}
|
(mr.name == mr_item.parent)
|
||||||
order by mr_item.item_code ASC
|
& (mr_item.item_code.isin(supplier_items))
|
||||||
limit {} offset {} """.format(
|
& (mr.material_request_type == "Purchase")
|
||||||
", ".join(["%s"] * len(supplier_items)), conditions, cint(page_len), cint(start)
|
& (mr.per_ordered < 99.99)
|
||||||
),
|
& (mr.docstatus == 1)
|
||||||
(*tuple(supplier_items), filters.get("company")),
|
& (mr.status != "Stopped")
|
||||||
as_dict=1,
|
& (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
|
return material_requests
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ frappe.listview_settings["Material Request"] = {
|
|||||||
return [__("Partially Received"), "yellow", "per_received,<,100"];
|
return [__("Partially Received"), "yellow", "per_received,<,100"];
|
||||||
} else if (doc.material_request_type == "Purchase" && flt(doc.per_received, precision) == 100) {
|
} else if (doc.material_request_type == "Purchase" && flt(doc.per_received, precision) == 100) {
|
||||||
return [__("Received"), "green", "per_received,=,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"];
|
return [__("Ordered"), "green", "per_ordered,=,100"];
|
||||||
} else if (doc.material_request_type == "Material Transfer") {
|
} else if (doc.material_request_type == "Material Transfer") {
|
||||||
return [__("Transferred"), "green", "per_ordered,=,100"];
|
return [__("Transferred"), "green", "per_ordered,=,100"];
|
||||||
@@ -43,8 +43,6 @@ frappe.listview_settings["Material Request"] = {
|
|||||||
return [__("Issued"), "green", "per_ordered,=,100"];
|
return [__("Issued"), "green", "per_ordered,=,100"];
|
||||||
} else if (doc.material_request_type == "Customer Provided") {
|
} else if (doc.material_request_type == "Customer Provided") {
|
||||||
return [__("Received"), "green", "per_ordered,=,100"];
|
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:
|
for perm in permissions:
|
||||||
perm.delete()
|
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):
|
def get_in_transit_warehouse(company):
|
||||||
if not frappe.db.exists("Warehouse Type", "Transit"):
|
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