Merge branch 'develop' into copy-emails-to-customer
This commit is contained in:
commit
50cff656b4
@ -118,6 +118,7 @@ class Account(NestedSet):
|
|||||||
self.validate_balance_must_be_debit_or_credit()
|
self.validate_balance_must_be_debit_or_credit()
|
||||||
self.validate_account_currency()
|
self.validate_account_currency()
|
||||||
self.validate_root_company_and_sync_account_to_children()
|
self.validate_root_company_and_sync_account_to_children()
|
||||||
|
self.validate_receivable_payable_account_type()
|
||||||
|
|
||||||
def validate_parent_child_account_type(self):
|
def validate_parent_child_account_type(self):
|
||||||
if self.parent_account:
|
if self.parent_account:
|
||||||
@ -188,6 +189,24 @@ class Account(NestedSet):
|
|||||||
"Balance Sheet" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss"
|
"Balance Sheet" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate_receivable_payable_account_type(self):
|
||||||
|
doc_before_save = self.get_doc_before_save()
|
||||||
|
receivable_payable_types = ["Receivable", "Payable"]
|
||||||
|
if (
|
||||||
|
doc_before_save
|
||||||
|
and doc_before_save.account_type in receivable_payable_types
|
||||||
|
and doc_before_save.account_type != self.account_type
|
||||||
|
):
|
||||||
|
# check for ledger entries
|
||||||
|
if frappe.db.get_all("GL Entry", filters={"account": self.name, "is_cancelled": 0}, limit=1):
|
||||||
|
msg = _(
|
||||||
|
"There are ledger entries against this account. Changing {0} to non-{1} in live system will cause incorrect output in 'Accounts {2}' report"
|
||||||
|
).format(
|
||||||
|
frappe.bold("Account Type"), doc_before_save.account_type, doc_before_save.account_type
|
||||||
|
)
|
||||||
|
frappe.msgprint(msg)
|
||||||
|
self.add_comment("Comment", msg)
|
||||||
|
|
||||||
def validate_root_details(self):
|
def validate_root_details(self):
|
||||||
doc_before_save = self.get_doc_before_save()
|
doc_before_save = self.get_doc_before_save()
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import unittest
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.test_runner import make_test_records
|
from frappe.test_runner import make_test_records
|
||||||
|
from frappe.utils import nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.account.account import (
|
from erpnext.accounts.doctype.account.account import (
|
||||||
InvalidAccountMergeError,
|
InvalidAccountMergeError,
|
||||||
@ -324,6 +325,19 @@ class TestAccount(unittest.TestCase):
|
|||||||
acc.account_currency = "USD"
|
acc.account_currency = "USD"
|
||||||
self.assertRaises(frappe.ValidationError, acc.save)
|
self.assertRaises(frappe.ValidationError, acc.save)
|
||||||
|
|
||||||
|
def test_account_balance(self):
|
||||||
|
from erpnext.accounts.utils import get_balance_on
|
||||||
|
|
||||||
|
if not frappe.db.exists("Account", "Test Percent Account %5 - _TC"):
|
||||||
|
acc = frappe.new_doc("Account")
|
||||||
|
acc.account_name = "Test Percent Account %5"
|
||||||
|
acc.parent_account = "Tax Assets - _TC"
|
||||||
|
acc.company = "_Test Company"
|
||||||
|
acc.insert()
|
||||||
|
|
||||||
|
balance = get_balance_on(account="Test Percent Account %5 - _TC", date=nowdate())
|
||||||
|
self.assertEqual(balance, 0)
|
||||||
|
|
||||||
|
|
||||||
def _make_test_records(verbose=None):
|
def _make_test_records(verbose=None):
|
||||||
from frappe.test_runner import make_test_objects
|
from frappe.test_runner import make_test_objects
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
"creation": "2013-06-24 15:49:57",
|
"creation": "2013-06-24 15:49:57",
|
||||||
"description": "Settings for Accounts",
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Other",
|
"document_type": "Other",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
@ -462,7 +461,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-20 09:37:47.650347",
|
"modified": "2024-01-30 14:04:26.553554",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, msgprint
|
from frappe import _, msgprint
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.query_builder.custom import ConstantColumn
|
||||||
from frappe.utils import flt, fmt_money, getdate
|
from frappe.utils import flt, fmt_money, getdate
|
||||||
|
from pypika import Order
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
|
|
||||||
@ -179,39 +181,62 @@ def get_payment_entries_for_bank_clearance(
|
|||||||
|
|
||||||
pos_sales_invoices, pos_purchase_invoices = [], []
|
pos_sales_invoices, pos_purchase_invoices = [], []
|
||||||
if include_pos_transactions:
|
if include_pos_transactions:
|
||||||
pos_sales_invoices = frappe.db.sql(
|
si_payment = frappe.qb.DocType("Sales Invoice Payment")
|
||||||
"""
|
si = frappe.qb.DocType("Sales Invoice")
|
||||||
select
|
acc = frappe.qb.DocType("Account")
|
||||||
"Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit,
|
|
||||||
si.posting_date, si.customer as against_account, sip.clearance_date,
|
|
||||||
account.account_currency, 0 as credit
|
|
||||||
from `tabSales Invoice Payment` sip, `tabSales Invoice` si, `tabAccount` account
|
|
||||||
where
|
|
||||||
sip.account=%(account)s and si.docstatus=1 and sip.parent = si.name
|
|
||||||
and account.name = sip.account and si.posting_date >= %(from)s and si.posting_date <= %(to)s
|
|
||||||
order by
|
|
||||||
si.posting_date ASC, si.name DESC
|
|
||||||
""",
|
|
||||||
{"account": account, "from": from_date, "to": to_date},
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
pos_purchase_invoices = frappe.db.sql(
|
pos_sales_invoices = (
|
||||||
"""
|
frappe.qb.from_(si_payment)
|
||||||
select
|
.inner_join(si)
|
||||||
"Purchase Invoice" as payment_document, pi.name as payment_entry, pi.paid_amount as credit,
|
.on(si_payment.parent == si.name)
|
||||||
pi.posting_date, pi.supplier as against_account, pi.clearance_date,
|
.inner_join(acc)
|
||||||
account.account_currency, 0 as debit
|
.on(si_payment.account == acc.name)
|
||||||
from `tabPurchase Invoice` pi, `tabAccount` account
|
.select(
|
||||||
where
|
ConstantColumn("Sales Invoice").as_("payment_document"),
|
||||||
pi.cash_bank_account=%(account)s and pi.docstatus=1 and account.name = pi.cash_bank_account
|
si.name.as_("payment_entry"),
|
||||||
and pi.posting_date >= %(from)s and pi.posting_date <= %(to)s
|
si_payment.reference_no.as_("cheque_number"),
|
||||||
order by
|
si_payment.amount.as_("debit"),
|
||||||
pi.posting_date ASC, pi.name DESC
|
si.posting_date,
|
||||||
""",
|
si.customer.as_("against_account"),
|
||||||
{"account": account, "from": from_date, "to": to_date},
|
si_payment.clearance_date,
|
||||||
as_dict=1,
|
acc.account_currency,
|
||||||
)
|
ConstantColumn(0).as_("credit"),
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(si.docstatus == 1)
|
||||||
|
& (si_payment.account == account)
|
||||||
|
& (si.posting_date >= from_date)
|
||||||
|
& (si.posting_date <= to_date)
|
||||||
|
)
|
||||||
|
.orderby(si.posting_date)
|
||||||
|
.orderby(si.name, order=Order.desc)
|
||||||
|
).run(as_dict=True)
|
||||||
|
|
||||||
|
pi = frappe.qb.DocType("Purchase Invoice")
|
||||||
|
|
||||||
|
pos_purchase_invoices = (
|
||||||
|
frappe.qb.from_(pi)
|
||||||
|
.inner_join(acc)
|
||||||
|
.on(pi.cash_bank_account == acc.name)
|
||||||
|
.select(
|
||||||
|
ConstantColumn("Purchase Invoice").as_("payment_document"),
|
||||||
|
pi.name.as_("payment_entry"),
|
||||||
|
pi.paid_amount.as_("credit"),
|
||||||
|
pi.posting_date,
|
||||||
|
pi.supplier.as_("against_account"),
|
||||||
|
pi.clearance_date,
|
||||||
|
acc.account_currency,
|
||||||
|
ConstantColumn(0).as_("debit"),
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(pi.docstatus == 1)
|
||||||
|
& (pi.cash_bank_account == account)
|
||||||
|
& (pi.posting_date >= from_date)
|
||||||
|
& (pi.posting_date <= to_date)
|
||||||
|
)
|
||||||
|
.orderby(pi.posting_date)
|
||||||
|
.orderby(pi.name, order=Order.desc)
|
||||||
|
).run(as_dict=True)
|
||||||
|
|
||||||
entries = (
|
entries = (
|
||||||
list(payment_entries)
|
list(payment_entries)
|
||||||
|
@ -80,7 +80,8 @@ class BankStatementImport(DataImport):
|
|||||||
from frappe.utils.background_jobs import is_job_enqueued
|
from frappe.utils.background_jobs import is_job_enqueued
|
||||||
from frappe.utils.scheduler import is_scheduler_inactive
|
from frappe.utils.scheduler import is_scheduler_inactive
|
||||||
|
|
||||||
if is_scheduler_inactive() and not frappe.flags.in_test:
|
run_now = frappe.flags.in_test or frappe.conf.developer_mode
|
||||||
|
if is_scheduler_inactive() and not run_now:
|
||||||
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
|
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
|
||||||
|
|
||||||
job_id = f"bank_statement_import::{self.name}"
|
job_id = f"bank_statement_import::{self.name}"
|
||||||
@ -97,7 +98,7 @@ class BankStatementImport(DataImport):
|
|||||||
google_sheets_url=self.google_sheets_url,
|
google_sheets_url=self.google_sheets_url,
|
||||||
bank=self.bank,
|
bank=self.bank,
|
||||||
template_options=self.template_options,
|
template_options=self.template_options,
|
||||||
now=frappe.conf.developer_mode or frappe.flags.in_test,
|
now=run_now,
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"autoname": "field:year",
|
"autoname": "field:year",
|
||||||
"creation": "2013-01-22 16:50:25",
|
"creation": "2013-01-22 16:50:25",
|
||||||
"description": "**Fiscal Year** represents a Financial Year. All accounting entries and other major transactions are tracked against **Fiscal Year**.",
|
"description": "Represents a Financial Year. All accounting entries and other major transactions are tracked against the Fiscal Year.",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
@ -82,11 +82,12 @@
|
|||||||
"icon": "fa fa-calendar",
|
"icon": "fa fa-calendar",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-01-17 13:06:01.608953",
|
"modified": "2024-01-30 12:35:38.645968",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Fiscal Year",
|
"name": "Fiscal Year",
|
||||||
"owner": "Administrator",
|
"naming_rule": "By fieldname",
|
||||||
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"create": 1,
|
"create": 1,
|
||||||
@ -130,5 +131,6 @@
|
|||||||
],
|
],
|
||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_field": "name",
|
"sort_field": "name",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
}
|
}
|
@ -150,6 +150,20 @@ class JournalEntry(AccountsController):
|
|||||||
if not self.title:
|
if not self.title:
|
||||||
self.title = self.get_title()
|
self.title = self.get_title()
|
||||||
|
|
||||||
|
def submit(self):
|
||||||
|
if len(self.accounts) > 100:
|
||||||
|
msgprint(_("The task has been enqueued as a background job."), alert=True)
|
||||||
|
self.queue_action("submit", timeout=4600)
|
||||||
|
else:
|
||||||
|
return self._submit()
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
if len(self.accounts) > 100:
|
||||||
|
msgprint(_("The task has been enqueued as a background job."), alert=True)
|
||||||
|
self.queue_action("cancel", timeout=4600)
|
||||||
|
else:
|
||||||
|
return self._cancel()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.validate_cheque_info()
|
self.validate_cheque_info()
|
||||||
self.check_credit_limit()
|
self.check_credit_limit()
|
||||||
|
@ -1,173 +1,77 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_import": 0,
|
"autoname": "field:distribution_id",
|
||||||
"allow_rename": 0,
|
"creation": "2013-01-10 16:34:05",
|
||||||
"autoname": "field:distribution_id",
|
"description": "Helps you distribute the Budget/Target across months if you have seasonality in your business.",
|
||||||
"beta": 0,
|
"doctype": "DocType",
|
||||||
"creation": "2013-01-10 16:34:05",
|
"engine": "InnoDB",
|
||||||
"custom": 0,
|
"field_order": [
|
||||||
"description": "**Monthly Distribution** helps you distribute the Budget/Target across months if you have seasonality in your business.",
|
"distribution_id",
|
||||||
"docstatus": 0,
|
"fiscal_year",
|
||||||
"doctype": "DocType",
|
"percentages"
|
||||||
"editable_grid": 0,
|
],
|
||||||
"engine": "InnoDB",
|
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"description": "Name of the Monthly Distribution",
|
||||||
"bold": 0,
|
"fieldname": "distribution_id",
|
||||||
"collapsible": 0,
|
"fieldtype": "Data",
|
||||||
"columns": 0,
|
"in_list_view": 1,
|
||||||
"description": "Name of the Monthly Distribution",
|
"label": "Distribution Name",
|
||||||
"fieldname": "distribution_id",
|
"oldfieldname": "distribution_id",
|
||||||
"fieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"hidden": 0,
|
"reqd": 1,
|
||||||
"ignore_user_permissions": 0,
|
"unique": 1
|
||||||
"ignore_xss_filter": 0,
|
},
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Distribution Name",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "distribution_id",
|
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"fieldname": "fiscal_year",
|
||||||
"bold": 0,
|
"fieldtype": "Link",
|
||||||
"collapsible": 0,
|
"in_filter": 1,
|
||||||
"columns": 0,
|
"in_list_view": 1,
|
||||||
"fieldname": "fiscal_year",
|
"in_standard_filter": 1,
|
||||||
"fieldtype": "Link",
|
"label": "Fiscal Year",
|
||||||
"hidden": 0,
|
"oldfieldname": "fiscal_year",
|
||||||
"ignore_user_permissions": 0,
|
"oldfieldtype": "Select",
|
||||||
"ignore_xss_filter": 0,
|
"options": "Fiscal Year",
|
||||||
"in_filter": 1,
|
"search_index": 1
|
||||||
"in_list_view": 1,
|
},
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Fiscal Year",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "fiscal_year",
|
|
||||||
"oldfieldtype": "Select",
|
|
||||||
"options": "Fiscal Year",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 1,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"fieldname": "percentages",
|
||||||
"bold": 0,
|
"fieldtype": "Table",
|
||||||
"collapsible": 0,
|
"label": "Monthly Distribution Percentages",
|
||||||
"columns": 0,
|
"oldfieldname": "budget_distribution_details",
|
||||||
"fieldname": "percentages",
|
"oldfieldtype": "Table",
|
||||||
"fieldtype": "Table",
|
"options": "Monthly Distribution Percentage"
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Monthly Distribution Percentages",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "budget_distribution_details",
|
|
||||||
"oldfieldtype": "Table",
|
|
||||||
"options": "Monthly Distribution Percentage",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hide_heading": 0,
|
"icon": "fa fa-bar-chart",
|
||||||
"hide_toolbar": 0,
|
"idx": 1,
|
||||||
"icon": "fa fa-bar-chart",
|
"links": [],
|
||||||
"idx": 1,
|
"modified": "2024-01-30 13:57:55.802744",
|
||||||
"image_view": 0,
|
"modified_by": "Administrator",
|
||||||
"in_create": 0,
|
"module": "Accounts",
|
||||||
|
"name": "Monthly Distribution",
|
||||||
"is_submittable": 0,
|
"naming_rule": "By fieldname",
|
||||||
"issingle": 0,
|
"owner": "Administrator",
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2016-11-21 14:54:35.998761",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Accounts",
|
|
||||||
"name": "Monthly Distribution",
|
|
||||||
"name_case": "Title Case",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"create": 1,
|
||||||
"apply_user_permissions": 0,
|
"delete": 1,
|
||||||
"cancel": 0,
|
"email": 1,
|
||||||
"create": 1,
|
"print": 1,
|
||||||
"delete": 1,
|
"read": 1,
|
||||||
"email": 1,
|
"report": 1,
|
||||||
"export": 0,
|
"role": "Accounts Manager",
|
||||||
"if_owner": 0,
|
"share": 1,
|
||||||
"import": 0,
|
|
||||||
"is_custom": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "Accounts Manager",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"permlevel": 2,
|
||||||
"apply_user_permissions": 0,
|
"read": 1,
|
||||||
"cancel": 0,
|
"report": 1,
|
||||||
"create": 0,
|
"role": "Accounts Manager"
|
||||||
"delete": 0,
|
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"is_custom": 0,
|
|
||||||
"permlevel": 2,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "Accounts Manager",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
"sort_field": "modified",
|
||||||
"read_only": 0,
|
"sort_order": "DESC",
|
||||||
"read_only_onload": 0,
|
"states": []
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
@ -1032,19 +1032,19 @@ class PaymentEntry(AccountsController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount)
|
base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount)
|
||||||
|
|
||||||
if self.payment_type == "Receive":
|
|
||||||
self.difference_amount = base_party_amount - self.base_received_amount
|
|
||||||
elif self.payment_type == "Pay":
|
|
||||||
self.difference_amount = self.base_paid_amount - base_party_amount
|
|
||||||
else:
|
|
||||||
self.difference_amount = self.base_paid_amount - flt(self.base_received_amount)
|
|
||||||
|
|
||||||
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
|
|
||||||
included_taxes = self.get_included_taxes()
|
included_taxes = self.get_included_taxes()
|
||||||
|
|
||||||
|
if self.payment_type == "Receive":
|
||||||
|
self.difference_amount = base_party_amount - self.base_received_amount + included_taxes
|
||||||
|
elif self.payment_type == "Pay":
|
||||||
|
self.difference_amount = self.base_paid_amount - base_party_amount - included_taxes
|
||||||
|
else:
|
||||||
|
self.difference_amount = self.base_paid_amount - flt(self.base_received_amount) - included_taxes
|
||||||
|
|
||||||
|
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
|
||||||
|
|
||||||
self.difference_amount = flt(
|
self.difference_amount = flt(
|
||||||
self.difference_amount - total_deductions - included_taxes, self.precision("difference_amount")
|
self.difference_amount - total_deductions, self.precision("difference_amount")
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_included_taxes(self):
|
def get_included_taxes(self):
|
||||||
|
@ -25,6 +25,10 @@ frappe.ui.form.on("Payment Request", "onload", function(frm, dt, dn){
|
|||||||
})
|
})
|
||||||
|
|
||||||
frappe.ui.form.on("Payment Request", "refresh", function(frm) {
|
frappe.ui.form.on("Payment Request", "refresh", function(frm) {
|
||||||
|
if(frm.doc.status == 'Failed'){
|
||||||
|
frm.set_intro(__("Failure: {0}", [frm.doc.failed_reason]), "red");
|
||||||
|
}
|
||||||
|
|
||||||
if(frm.doc.payment_request_type == 'Inward' && frm.doc.payment_channel !== "Phone" &&
|
if(frm.doc.payment_request_type == 'Inward' && frm.doc.payment_channel !== "Phone" &&
|
||||||
!in_list(["Initiated", "Paid"], frm.doc.status) && !frm.doc.__islocal && frm.doc.docstatus==1){
|
!in_list(["Initiated", "Paid"], frm.doc.status) && !frm.doc.__islocal && frm.doc.docstatus==1){
|
||||||
frm.add_custom_button(__('Resend Payment Email'), function(){
|
frm.add_custom_button(__('Resend Payment Email'), function(){
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"payment_request_type",
|
"payment_request_type",
|
||||||
"transaction_date",
|
"transaction_date",
|
||||||
|
"failed_reason",
|
||||||
"column_break_2",
|
"column_break_2",
|
||||||
"naming_series",
|
"naming_series",
|
||||||
"mode_of_payment",
|
"mode_of_payment",
|
||||||
@ -389,13 +390,22 @@
|
|||||||
"options": "Payment Request",
|
"options": "Payment Request",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "failed_reason",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Reason for Failure",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-09-27 09:51:42.277638",
|
"modified": "2024-01-20 00:37:06.988919",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Request",
|
"name": "Payment Request",
|
||||||
@ -433,4 +443,4 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": []
|
"states": []
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,9 @@ frappe.listview_settings['Payment Request'] = {
|
|||||||
else if(doc.status == "Paid") {
|
else if(doc.status == "Paid") {
|
||||||
return [__("Paid"), "blue", "status,=,Paid"];
|
return [__("Paid"), "blue", "status,=,Paid"];
|
||||||
}
|
}
|
||||||
|
else if(doc.status == "Failed") {
|
||||||
|
return [__("Failed"), "red", "status,=,Failed"];
|
||||||
|
}
|
||||||
else if(doc.status == "Cancelled") {
|
else if(doc.status == "Cancelled") {
|
||||||
return [__("Cancelled"), "red", "status,=,Cancelled"];
|
return [__("Cancelled"), "red", "status,=,Cancelled"];
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
inv.save()
|
inv.save()
|
||||||
|
|
||||||
self.assertEqual(inv.net_total, 4298.25)
|
self.assertEqual(inv.net_total, 4298.24)
|
||||||
self.assertEqual(inv.grand_total, 4900.00)
|
self.assertEqual(inv.grand_total, 4900.00)
|
||||||
|
|
||||||
def test_tax_calculation_with_multiple_items(self):
|
def test_tax_calculation_with_multiple_items(self):
|
||||||
|
@ -351,7 +351,7 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
|
|||||||
inv.load_from_db()
|
inv.load_from_db()
|
||||||
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
|
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
|
||||||
self.assertEqual(consolidated_invoice.status, "Return")
|
self.assertEqual(consolidated_invoice.status, "Return")
|
||||||
self.assertEqual(consolidated_invoice.rounding_adjustment, -0.001)
|
self.assertEqual(consolidated_invoice.rounding_adjustment, -0.002)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
|
@ -120,18 +120,6 @@ def get_statement_dict(doc, get_statement_dict=False):
|
|||||||
statement_dict = {}
|
statement_dict = {}
|
||||||
ageing = ""
|
ageing = ""
|
||||||
|
|
||||||
err_journals = None
|
|
||||||
if doc.report == "General Ledger" and doc.ignore_exchange_rate_revaluation_journals:
|
|
||||||
err_journals = frappe.db.get_all(
|
|
||||||
"Journal Entry",
|
|
||||||
filters={
|
|
||||||
"company": doc.company,
|
|
||||||
"docstatus": 1,
|
|
||||||
"voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]),
|
|
||||||
},
|
|
||||||
as_list=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
for entry in doc.customers:
|
for entry in doc.customers:
|
||||||
if doc.include_ageing:
|
if doc.include_ageing:
|
||||||
ageing = set_ageing(doc, entry)
|
ageing = set_ageing(doc, entry)
|
||||||
@ -144,8 +132,8 @@ def get_statement_dict(doc, get_statement_dict=False):
|
|||||||
)
|
)
|
||||||
|
|
||||||
filters = get_common_filters(doc)
|
filters = get_common_filters(doc)
|
||||||
if err_journals:
|
if doc.ignore_exchange_rate_revaluation_journals:
|
||||||
filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
|
filters.update({"ignore_err": True})
|
||||||
|
|
||||||
if doc.report == "General Ledger":
|
if doc.report == "General Ledger":
|
||||||
filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency))
|
filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency))
|
||||||
|
@ -1995,6 +1995,21 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
|||||||
|
|
||||||
self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC")
|
self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC")
|
||||||
|
|
||||||
|
def test_debit_note_with_account_mismatch(self):
|
||||||
|
new_creditors = create_account(
|
||||||
|
parent_account="Accounts Payable - _TC",
|
||||||
|
account_name="Creditors 2",
|
||||||
|
company="_Test Company",
|
||||||
|
account_type="Payable",
|
||||||
|
)
|
||||||
|
pi = make_purchase_invoice(qty=1, rate=1000)
|
||||||
|
dr_note = make_purchase_invoice(
|
||||||
|
qty=-1, rate=1000, is_return=1, return_against=pi.name, do_not_save=True
|
||||||
|
)
|
||||||
|
dr_note.credit_to = new_creditors
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, dr_note.save)
|
||||||
|
|
||||||
def test_debit_note_without_item(self):
|
def test_debit_note_without_item(self):
|
||||||
pi = make_purchase_invoice(item_name="_Test Item", qty=10, do_not_submit=True)
|
pi = make_purchase_invoice(item_name="_Test Item", qty=10, do_not_submit=True)
|
||||||
pi.items[0].item_code = ""
|
pi.items[0].item_code = ""
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"creation": "2013-01-10 16:34:08",
|
"creation": "2013-01-10 16:34:08",
|
||||||
"description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.\n10. Add or Deduct: Whether you want to add or deduct the tax.",
|
"description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain a list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\", etc.",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
@ -77,7 +77,7 @@
|
|||||||
"icon": "fa fa-money",
|
"icon": "fa fa-money",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-05-16 16:15:29.059370",
|
"modified": "2024-01-30 13:08:09.537242",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Taxes and Charges Template",
|
"name": "Purchase Taxes and Charges Template",
|
||||||
|
@ -323,7 +323,8 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
si.insert()
|
si.insert()
|
||||||
|
|
||||||
# with inclusive tax
|
# with inclusive tax
|
||||||
self.assertEqual(si.items[0].net_amount, 3947.368421052631)
|
self.assertEqual(si.items[0].net_amount, 3947.37)
|
||||||
|
self.assertEqual(si.net_total, si.base_net_total)
|
||||||
self.assertEqual(si.net_total, 3947.37)
|
self.assertEqual(si.net_total, 3947.37)
|
||||||
self.assertEqual(si.grand_total, 5000)
|
self.assertEqual(si.grand_total, 5000)
|
||||||
|
|
||||||
@ -667,7 +668,7 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
62.5,
|
62.5,
|
||||||
625.0,
|
625.0,
|
||||||
50,
|
50,
|
||||||
499.97600115194473,
|
499.98,
|
||||||
],
|
],
|
||||||
"_Test Item Home Desktop 200": [
|
"_Test Item Home Desktop 200": [
|
||||||
190.66,
|
190.66,
|
||||||
@ -678,7 +679,7 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
190.66,
|
190.66,
|
||||||
953.3,
|
953.3,
|
||||||
150,
|
150,
|
||||||
749.9968530500239,
|
750,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -691,20 +692,21 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
self.assertEqual(d.get(k), expected_values[d.item_code][i])
|
self.assertEqual(d.get(k), expected_values[d.item_code][i])
|
||||||
|
|
||||||
# check net total
|
# check net total
|
||||||
self.assertEqual(si.net_total, 1249.97)
|
self.assertEqual(si.base_net_total, si.net_total)
|
||||||
|
self.assertEqual(si.net_total, 1249.98)
|
||||||
self.assertEqual(si.total, 1578.3)
|
self.assertEqual(si.total, 1578.3)
|
||||||
|
|
||||||
# check tax calculation
|
# check tax calculation
|
||||||
expected_values = {
|
expected_values = {
|
||||||
"keys": ["tax_amount", "total"],
|
"keys": ["tax_amount", "total"],
|
||||||
"_Test Account Excise Duty - _TC": [140, 1389.97],
|
"_Test Account Excise Duty - _TC": [140, 1389.98],
|
||||||
"_Test Account Education Cess - _TC": [2.8, 1392.77],
|
"_Test Account Education Cess - _TC": [2.8, 1392.78],
|
||||||
"_Test Account S&H Education Cess - _TC": [1.4, 1394.17],
|
"_Test Account S&H Education Cess - _TC": [1.4, 1394.18],
|
||||||
"_Test Account CST - _TC": [27.88, 1422.05],
|
"_Test Account CST - _TC": [27.88, 1422.06],
|
||||||
"_Test Account VAT - _TC": [156.25, 1578.30],
|
"_Test Account VAT - _TC": [156.25, 1578.31],
|
||||||
"_Test Account Customs Duty - _TC": [125, 1703.30],
|
"_Test Account Customs Duty - _TC": [125, 1703.31],
|
||||||
"_Test Account Shipping Charges - _TC": [100, 1803.30],
|
"_Test Account Shipping Charges - _TC": [100, 1803.31],
|
||||||
"_Test Account Discount - _TC": [-180.33, 1622.97],
|
"_Test Account Discount - _TC": [-180.33, 1622.98],
|
||||||
}
|
}
|
||||||
|
|
||||||
for d in si.get("taxes"):
|
for d in si.get("taxes"):
|
||||||
@ -740,7 +742,7 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
"base_rate": 2500,
|
"base_rate": 2500,
|
||||||
"base_amount": 25000,
|
"base_amount": 25000,
|
||||||
"net_rate": 40,
|
"net_rate": 40,
|
||||||
"net_amount": 399.9808009215558,
|
"net_amount": 399.98,
|
||||||
"base_net_rate": 2000,
|
"base_net_rate": 2000,
|
||||||
"base_net_amount": 19999,
|
"base_net_amount": 19999,
|
||||||
},
|
},
|
||||||
@ -754,7 +756,7 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
"base_rate": 7500,
|
"base_rate": 7500,
|
||||||
"base_amount": 37500,
|
"base_amount": 37500,
|
||||||
"net_rate": 118.01,
|
"net_rate": 118.01,
|
||||||
"net_amount": 590.0531205155963,
|
"net_amount": 590.05,
|
||||||
"base_net_rate": 5900.5,
|
"base_net_rate": 5900.5,
|
||||||
"base_net_amount": 29502.5,
|
"base_net_amount": 29502.5,
|
||||||
},
|
},
|
||||||
@ -792,8 +794,13 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertEqual(si.base_grand_total, 60795)
|
self.assertEqual(si.base_grand_total, 60795)
|
||||||
self.assertEqual(si.grand_total, 1215.90)
|
self.assertEqual(si.grand_total, 1215.90)
|
||||||
self.assertEqual(si.rounding_adjustment, 0.01)
|
# no rounding adjustment as the Smallest Currency Fraction Value of USD is 0.01
|
||||||
self.assertEqual(si.base_rounding_adjustment, 0.50)
|
if frappe.db.get_value("Currency", "USD", "smallest_currency_fraction_value") < 0.01:
|
||||||
|
self.assertEqual(si.rounding_adjustment, 0.10)
|
||||||
|
self.assertEqual(si.base_rounding_adjustment, 5.0)
|
||||||
|
else:
|
||||||
|
self.assertEqual(si.rounding_adjustment, 0.0)
|
||||||
|
self.assertEqual(si.base_rounding_adjustment, 0.0)
|
||||||
|
|
||||||
def test_outstanding(self):
|
def test_outstanding(self):
|
||||||
w = self.make()
|
w = self.make()
|
||||||
@ -1543,6 +1550,19 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
self.assertEqual(frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount"), -1000)
|
self.assertEqual(frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount"), -1000)
|
||||||
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 2500)
|
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 2500)
|
||||||
|
|
||||||
|
def test_return_invoice_with_account_mismatch(self):
|
||||||
|
debtors2 = create_account(
|
||||||
|
parent_account="Accounts Receivable - _TC",
|
||||||
|
account_name="Debtors 2",
|
||||||
|
company="_Test Company",
|
||||||
|
account_type="Receivable",
|
||||||
|
)
|
||||||
|
si = create_sales_invoice(qty=1, rate=1000)
|
||||||
|
cr_note = create_sales_invoice(
|
||||||
|
qty=-1, rate=1000, is_return=1, return_against=si.name, debit_to=debtors2, do_not_save=True
|
||||||
|
)
|
||||||
|
self.assertRaises(frappe.ValidationError, cr_note.save)
|
||||||
|
|
||||||
def test_gle_made_when_asset_is_returned(self):
|
def test_gle_made_when_asset_is_returned(self):
|
||||||
create_asset_data()
|
create_asset_data()
|
||||||
asset = create_asset(item_code="Macbook Pro")
|
asset = create_asset(item_code="Macbook Pro")
|
||||||
@ -2082,7 +2102,7 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
|
|
||||||
def test_rounding_adjustment_2(self):
|
def test_rounding_adjustment_2(self):
|
||||||
si = create_sales_invoice(rate=400, do_not_save=True)
|
si = create_sales_invoice(rate=400, do_not_save=True)
|
||||||
for rate in [400, 600, 100]:
|
for rate in [400.25, 600.30, 100.65]:
|
||||||
si.append(
|
si.append(
|
||||||
"items",
|
"items",
|
||||||
{
|
{
|
||||||
@ -2108,17 +2128,18 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
)
|
)
|
||||||
si.save()
|
si.save()
|
||||||
si.submit()
|
si.submit()
|
||||||
self.assertEqual(si.net_total, 1271.19)
|
self.assertEqual(si.net_total, si.base_net_total)
|
||||||
self.assertEqual(si.grand_total, 1500)
|
self.assertEqual(si.net_total, 1272.20)
|
||||||
self.assertEqual(si.total_taxes_and_charges, 228.82)
|
self.assertEqual(si.grand_total, 1501.20)
|
||||||
self.assertEqual(si.rounding_adjustment, -0.01)
|
self.assertEqual(si.total_taxes_and_charges, 229)
|
||||||
|
self.assertEqual(si.rounding_adjustment, -0.20)
|
||||||
|
|
||||||
expected_values = [
|
expected_values = [
|
||||||
["_Test Account Service Tax - _TC", 0.0, 114.41],
|
["_Test Account Service Tax - _TC", 0.0, 114.50],
|
||||||
["_Test Account VAT - _TC", 0.0, 114.41],
|
["_Test Account VAT - _TC", 0.0, 114.50],
|
||||||
[si.debit_to, 1500, 0.0],
|
[si.debit_to, 1501, 0.0],
|
||||||
["Round Off - _TC", 0.01, 0.01],
|
["Round Off - _TC", 0.20, 0.0],
|
||||||
["Sales - _TC", 0.0, 1271.18],
|
["Sales - _TC", 0.0, 1272.20],
|
||||||
]
|
]
|
||||||
|
|
||||||
gl_entries = frappe.db.sql(
|
gl_entries = frappe.db.sql(
|
||||||
@ -2176,7 +2197,8 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
|
|
||||||
si.save()
|
si.save()
|
||||||
si.submit()
|
si.submit()
|
||||||
self.assertEqual(si.net_total, 4007.16)
|
self.assertEqual(si.net_total, si.base_net_total)
|
||||||
|
self.assertEqual(si.net_total, 4007.15)
|
||||||
self.assertEqual(si.grand_total, 4488.02)
|
self.assertEqual(si.grand_total, 4488.02)
|
||||||
self.assertEqual(si.total_taxes_and_charges, 480.86)
|
self.assertEqual(si.total_taxes_and_charges, 480.86)
|
||||||
self.assertEqual(si.rounding_adjustment, -0.02)
|
self.assertEqual(si.rounding_adjustment, -0.02)
|
||||||
@ -2188,7 +2210,7 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
["_Test Account Service Tax - _TC", 0.0, 240.43],
|
["_Test Account Service Tax - _TC", 0.0, 240.43],
|
||||||
["_Test Account VAT - _TC", 0.0, 240.43],
|
["_Test Account VAT - _TC", 0.0, 240.43],
|
||||||
["Sales - _TC", 0.0, 4007.15],
|
["Sales - _TC", 0.0, 4007.15],
|
||||||
["Round Off - _TC", 0.02, 0.01],
|
["Round Off - _TC", 0.01, 0.0],
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"default",
|
"default",
|
||||||
"mode_of_payment",
|
"mode_of_payment",
|
||||||
"amount",
|
"amount",
|
||||||
|
"reference_no",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"account",
|
"account",
|
||||||
"type",
|
"type",
|
||||||
@ -75,11 +76,16 @@
|
|||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Default",
|
"label": "Default",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reference_no",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Reference No"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-08-03 12:45:39.986598",
|
"modified": "2024-01-23 16:20:06.436979",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Payment",
|
"name": "Sales Invoice Payment",
|
||||||
@ -87,5 +93,6 @@
|
|||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
}
|
}
|
@ -23,6 +23,7 @@ class SalesInvoicePayment(Document):
|
|||||||
parent: DF.Data
|
parent: DF.Data
|
||||||
parentfield: DF.Data
|
parentfield: DF.Data
|
||||||
parenttype: DF.Data
|
parenttype: DF.Data
|
||||||
|
reference_no: DF.Data | None
|
||||||
type: DF.ReadOnly | None
|
type: DF.ReadOnly | None
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"creation": "2013-01-10 16:34:09",
|
"creation": "2013-01-10 16:34:09",
|
||||||
"description": "Standard tax template that can be applied to all Sales Transactions. This template can contain list of tax heads and also other expense / income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.",
|
"description": "Standard tax template that can be applied to all Sales Transactions. This template can contain a list of tax heads and also other expense/income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
@ -79,7 +79,7 @@
|
|||||||
"icon": "fa fa-money",
|
"icon": "fa fa-money",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-05-16 16:14:52.061672",
|
"modified": "2024-01-30 13:07:28.801104",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Taxes and Charges Template",
|
"name": "Sales Taxes and Charges Template",
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
<h3>{{ _("Fiscal Year") }}</h3>
|
||||||
|
|
||||||
|
<p>{{ _("New fiscal year created :- ") }} {{ doc.name }}</p>
|
@ -11,19 +11,21 @@
|
|||||||
"event": "New",
|
"event": "New",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"message": "<h3>{{_(\"Fiscal Year\")}}</h3>\n\n<p>{{ _(\"New fiscal year created :- \") }} {{ doc.name }}</p>",
|
"message_type": "HTML",
|
||||||
"modified": "2018-04-25 14:30:38.588534",
|
"modified": "2023-11-17 08:54:51.532104",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Notification for new fiscal year",
|
"name": "Notification for new fiscal year",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"recipients": [
|
"recipients": [
|
||||||
{
|
{
|
||||||
"email_by_role": "Accounts User"
|
"receiver_by_role": "Accounts User"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"email_by_role": "Accounts Manager"
|
"receiver_by_role": "Accounts Manager"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"send_system_notification": 0,
|
||||||
|
"send_to_all_assignees": 0,
|
||||||
"subject": "Notification for new fiscal year {{ doc.name }}"
|
"subject": "Notification for new fiscal year {{ doc.name }}"
|
||||||
}
|
}
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
<h3>{{_("Fiscal Year")}}</h3>
|
|
||||||
|
|
||||||
<p>{{ _("New fiscal year created :- ") }} {{ doc.name }}</p>
|
|
@ -8,6 +8,20 @@ frappe.query_reports["Balance Sheet"] = $.extend(
|
|||||||
|
|
||||||
erpnext.utils.add_dimensions("Balance Sheet", 10);
|
erpnext.utils.add_dimensions("Balance Sheet", 10);
|
||||||
|
|
||||||
|
frappe.query_reports["Balance Sheet"]["filters"].push(
|
||||||
|
{
|
||||||
|
"fieldname": "selected_view",
|
||||||
|
"label": __("Select View"),
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": [
|
||||||
|
{ "value": "Report", "label": __("Report View") },
|
||||||
|
{ "value": "Growth", "label": __("Growth View") }
|
||||||
|
],
|
||||||
|
"default": "Report",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
frappe.query_reports["Balance Sheet"]["filters"].push({
|
frappe.query_reports["Balance Sheet"]["filters"].push({
|
||||||
fieldname: "accumulated_values",
|
fieldname: "accumulated_values",
|
||||||
label: __("Accumulated Values"),
|
label: __("Accumulated Values"),
|
||||||
|
@ -203,8 +203,14 @@ frappe.query_reports["General Ledger"] = {
|
|||||||
"fieldname": "show_remarks",
|
"fieldname": "show_remarks",
|
||||||
"label": __("Show Remarks"),
|
"label": __("Show Remarks"),
|
||||||
"fieldtype": "Check"
|
"fieldtype": "Check"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "ignore_err",
|
||||||
|
"label": __("Ignore Exchange Rate Revaluation Journals"),
|
||||||
|
"fieldtype": "Check"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,6 +241,19 @@ def get_conditions(filters):
|
|||||||
if filters.get("against_voucher_no"):
|
if filters.get("against_voucher_no"):
|
||||||
conditions.append("against_voucher=%(against_voucher_no)s")
|
conditions.append("against_voucher=%(against_voucher_no)s")
|
||||||
|
|
||||||
|
if filters.get("ignore_err"):
|
||||||
|
err_journals = frappe.db.get_all(
|
||||||
|
"Journal Entry",
|
||||||
|
filters={
|
||||||
|
"company": filters.get("company"),
|
||||||
|
"docstatus": 1,
|
||||||
|
"voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]),
|
||||||
|
},
|
||||||
|
as_list=True,
|
||||||
|
)
|
||||||
|
if err_journals:
|
||||||
|
filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
|
||||||
|
|
||||||
if filters.get("voucher_no_not_in"):
|
if filters.get("voucher_no_not_in"):
|
||||||
conditions.append("voucher_no not in %(voucher_no_not_in)s")
|
conditions.append("voucher_no not in %(voucher_no_not_in)s")
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests.utils import FrappeTestCase
|
||||||
from frappe.utils import today
|
from frappe.utils import flt, today
|
||||||
|
|
||||||
from erpnext.accounts.report.general_ledger.general_ledger import execute
|
from erpnext.accounts.report.general_ledger.general_ledger import execute
|
||||||
|
|
||||||
@ -148,3 +148,105 @@ class TestGeneralLedger(FrappeTestCase):
|
|||||||
self.assertEqual(data[2]["credit"], 900)
|
self.assertEqual(data[2]["credit"], 900)
|
||||||
self.assertEqual(data[3]["debit"], 100)
|
self.assertEqual(data[3]["debit"], 100)
|
||||||
self.assertEqual(data[3]["credit"], 100)
|
self.assertEqual(data[3]["credit"], 100)
|
||||||
|
|
||||||
|
def test_ignore_exchange_rate_journals_filter(self):
|
||||||
|
# create a new account with USD currency
|
||||||
|
account_name = "Test Debtors USD"
|
||||||
|
company = "_Test Company"
|
||||||
|
account = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"account_name": account_name,
|
||||||
|
"is_group": 0,
|
||||||
|
"company": company,
|
||||||
|
"root_type": "Asset",
|
||||||
|
"report_type": "Balance Sheet",
|
||||||
|
"account_currency": "USD",
|
||||||
|
"parent_account": "Accounts Receivable - _TC",
|
||||||
|
"account_type": "Receivable",
|
||||||
|
"doctype": "Account",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
account.insert(ignore_if_duplicate=True)
|
||||||
|
# create a JV to debit 1000 USD at 75 exchange rate
|
||||||
|
jv = frappe.new_doc("Journal Entry")
|
||||||
|
jv.posting_date = today()
|
||||||
|
jv.company = company
|
||||||
|
jv.multi_currency = 1
|
||||||
|
jv.cost_center = "_Test Cost Center - _TC"
|
||||||
|
jv.set(
|
||||||
|
"accounts",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"account": account.name,
|
||||||
|
"party_type": "Customer",
|
||||||
|
"party": "_Test Customer USD",
|
||||||
|
"debit_in_account_currency": 1000,
|
||||||
|
"credit_in_account_currency": 0,
|
||||||
|
"exchange_rate": 75,
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"account": "Cash - _TC",
|
||||||
|
"debit_in_account_currency": 0,
|
||||||
|
"credit_in_account_currency": 75000,
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
jv.save()
|
||||||
|
jv.submit()
|
||||||
|
|
||||||
|
revaluation = frappe.new_doc("Exchange Rate Revaluation")
|
||||||
|
revaluation.posting_date = today()
|
||||||
|
revaluation.company = company
|
||||||
|
accounts = revaluation.get_accounts_data()
|
||||||
|
revaluation.extend("accounts", accounts)
|
||||||
|
row = revaluation.accounts[0]
|
||||||
|
row.new_exchange_rate = 83
|
||||||
|
row.new_balance_in_base_currency = flt(
|
||||||
|
row.new_exchange_rate * flt(row.balance_in_account_currency)
|
||||||
|
)
|
||||||
|
row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency)
|
||||||
|
revaluation.set_total_gain_loss()
|
||||||
|
revaluation = revaluation.save().submit()
|
||||||
|
|
||||||
|
# post journal entry for Revaluation doc
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Company", company, "unrealized_exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC"
|
||||||
|
)
|
||||||
|
revaluation_jv = revaluation.make_jv_for_revaluation()
|
||||||
|
revaluation_jv.cost_center = "_Test Cost Center - _TC"
|
||||||
|
for acc in revaluation_jv.get("accounts"):
|
||||||
|
acc.cost_center = "_Test Cost Center - _TC"
|
||||||
|
revaluation_jv.save()
|
||||||
|
revaluation_jv.submit()
|
||||||
|
|
||||||
|
# With ignore_err enabled
|
||||||
|
columns, data = execute(
|
||||||
|
frappe._dict(
|
||||||
|
{
|
||||||
|
"company": company,
|
||||||
|
"from_date": today(),
|
||||||
|
"to_date": today(),
|
||||||
|
"account": [account.name],
|
||||||
|
"group_by": "Group by Voucher (Consolidated)",
|
||||||
|
"ignore_err": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertNotIn(revaluation_jv.name, set([x.voucher_no for x in data]))
|
||||||
|
|
||||||
|
# Without ignore_err enabled
|
||||||
|
columns, data = execute(
|
||||||
|
frappe._dict(
|
||||||
|
{
|
||||||
|
"company": company,
|
||||||
|
"from_date": today(),
|
||||||
|
"to_date": today(),
|
||||||
|
"account": [account.name],
|
||||||
|
"group_by": "Group by Voucher (Consolidated)",
|
||||||
|
"ignore_err": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertIn(revaluation_jv.name, set([x.voucher_no for x in data]))
|
||||||
|
@ -8,6 +8,21 @@ frappe.query_reports["Profit and Loss Statement"] = $.extend(
|
|||||||
|
|
||||||
erpnext.utils.add_dimensions("Profit and Loss Statement", 10);
|
erpnext.utils.add_dimensions("Profit and Loss Statement", 10);
|
||||||
|
|
||||||
|
frappe.query_reports["Profit and Loss Statement"]["filters"].push(
|
||||||
|
{
|
||||||
|
"fieldname": "selected_view",
|
||||||
|
"label": __("Select View"),
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": [
|
||||||
|
{ "value": "Report", "label": __("Report View") },
|
||||||
|
{ "value": "Growth", "label": __("Growth View") },
|
||||||
|
{ "value": "Margin", "label": __("Margin View") },
|
||||||
|
],
|
||||||
|
"default": "Report",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
frappe.query_reports["Profit and Loss Statement"]["filters"].push({
|
frappe.query_reports["Profit and Loss Statement"]["filters"].push({
|
||||||
fieldname: "accumulated_values",
|
fieldname: "accumulated_values",
|
||||||
label: __("Accumulated Values"),
|
label: __("Accumulated Values"),
|
||||||
|
@ -237,7 +237,7 @@ def get_balance_on(
|
|||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
cond.append("""gle.cost_center = %s """ % (frappe.db.escape(cost_center, percent=False),))
|
cond.append("""gle.cost_center = %s """ % (frappe.db.escape(cost_center),))
|
||||||
|
|
||||||
if account:
|
if account:
|
||||||
if not (frappe.flags.ignore_account_permission or ignore_account_permission):
|
if not (frappe.flags.ignore_account_permission or ignore_account_permission):
|
||||||
@ -258,7 +258,7 @@ def get_balance_on(
|
|||||||
if acc.account_currency == frappe.get_cached_value("Company", acc.company, "default_currency"):
|
if acc.account_currency == frappe.get_cached_value("Company", acc.company, "default_currency"):
|
||||||
in_account_currency = False
|
in_account_currency = False
|
||||||
else:
|
else:
|
||||||
cond.append("""gle.account = %s """ % (frappe.db.escape(account, percent=False),))
|
cond.append("""gle.account = %s """ % (frappe.db.escape(account),))
|
||||||
|
|
||||||
if account_type:
|
if account_type:
|
||||||
accounts = frappe.db.get_all(
|
accounts = frappe.db.get_all(
|
||||||
@ -278,11 +278,11 @@ def get_balance_on(
|
|||||||
if party_type and party:
|
if party_type and party:
|
||||||
cond.append(
|
cond.append(
|
||||||
"""gle.party_type = %s and gle.party = %s """
|
"""gle.party_type = %s and gle.party = %s """
|
||||||
% (frappe.db.escape(party_type), frappe.db.escape(party, percent=False))
|
% (frappe.db.escape(party_type), frappe.db.escape(party))
|
||||||
)
|
)
|
||||||
|
|
||||||
if company:
|
if company:
|
||||||
cond.append("""gle.company = %s """ % (frappe.db.escape(company, percent=False)))
|
cond.append("""gle.company = %s """ % (frappe.db.escape(company)))
|
||||||
|
|
||||||
if account or (party_type and party) or account_type:
|
if account or (party_type and party) or account_type:
|
||||||
precision = get_currency_precision()
|
precision = get_currency_precision()
|
||||||
@ -348,7 +348,7 @@ def get_count_on(account, fieldname, date):
|
|||||||
% (acc.lft, acc.rgt)
|
% (acc.lft, acc.rgt)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
cond.append("""gle.account = %s """ % (frappe.db.escape(account, percent=False),))
|
cond.append("""gle.account = %s """ % (frappe.db.escape(account),))
|
||||||
|
|
||||||
entries = frappe.db.sql(
|
entries = frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
"creation": "2013-06-25 11:04:03",
|
"creation": "2013-06-25 11:04:03",
|
||||||
"description": "Settings for Buying Module",
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Other",
|
"document_type": "Other",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
@ -152,6 +151,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "1",
|
"default": "1",
|
||||||
|
"depends_on": "eval: frappe.boot.versions && frappe.boot.versions.payments",
|
||||||
"fieldname": "show_pay_button",
|
"fieldname": "show_pay_button",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Show Pay Button in Purchase Order Portal"
|
"label": "Show Pay Button in Purchase Order Portal"
|
||||||
@ -214,7 +214,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-01-12 16:42:01.894346",
|
"modified": "2024-01-31 13:34:18.101256",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Buying Settings",
|
"name": "Buying Settings",
|
||||||
|
@ -202,6 +202,7 @@ class AccountsController(TransactionBase):
|
|||||||
self.validate_party()
|
self.validate_party()
|
||||||
self.validate_currency()
|
self.validate_currency()
|
||||||
self.validate_party_account_currency()
|
self.validate_party_account_currency()
|
||||||
|
self.validate_return_against_account()
|
||||||
|
|
||||||
if self.doctype in ["Purchase Invoice", "Sales Invoice"]:
|
if self.doctype in ["Purchase Invoice", "Sales Invoice"]:
|
||||||
if invalid_advances := [
|
if invalid_advances := [
|
||||||
@ -350,6 +351,20 @@ class AccountsController(TransactionBase):
|
|||||||
for bundle in bundles:
|
for bundle in bundles:
|
||||||
frappe.delete_doc("Serial and Batch Bundle", bundle.name)
|
frappe.delete_doc("Serial and Batch Bundle", bundle.name)
|
||||||
|
|
||||||
|
def validate_return_against_account(self):
|
||||||
|
if (
|
||||||
|
self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against
|
||||||
|
):
|
||||||
|
cr_dr_account_field = "debit_to" if self.doctype == "Sales Invoice" else "credit_to"
|
||||||
|
cr_dr_account_label = "Debit To" if self.doctype == "Sales Invoice" else "Credit To"
|
||||||
|
cr_dr_account = self.get(cr_dr_account_field)
|
||||||
|
if frappe.get_value(self.doctype, self.return_against, cr_dr_account_field) != cr_dr_account:
|
||||||
|
frappe.throw(
|
||||||
|
_("'{0}' account: '{1}' should match the Return Against Invoice").format(
|
||||||
|
frappe.bold(cr_dr_account_label), frappe.bold(cr_dr_account)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def validate_deferred_income_expense_account(self):
|
def validate_deferred_income_expense_account(self):
|
||||||
field_map = {
|
field_map = {
|
||||||
"Sales Invoice": "deferred_revenue_account",
|
"Sales Invoice": "deferred_revenue_account",
|
||||||
|
@ -99,7 +99,8 @@ status_map = {
|
|||||||
],
|
],
|
||||||
"Purchase Receipt": [
|
"Purchase Receipt": [
|
||||||
["Draft", None],
|
["Draft", None],
|
||||||
["To Bill", "eval:self.per_billed < 100 and self.docstatus == 1"],
|
["To Bill", "eval:self.per_billed == 0 and self.docstatus == 1"],
|
||||||
|
["Partly Billed", "eval:self.per_billed > 0 and self.per_billed < 100 and self.docstatus == 1"],
|
||||||
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
|
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
|
||||||
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
|
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
|
||||||
["Cancelled", "eval:self.docstatus==2"],
|
["Cancelled", "eval:self.docstatus==2"],
|
||||||
|
@ -260,18 +260,22 @@ class SubcontractingController(StockController):
|
|||||||
return frappe.get_all(f"{doctype}", fields=fields, filters=filters)
|
return frappe.get_all(f"{doctype}", fields=fields, filters=filters)
|
||||||
|
|
||||||
def __get_consumed_items(self, doctype, receipt_items):
|
def __get_consumed_items(self, doctype, receipt_items):
|
||||||
|
fields = [
|
||||||
|
"serial_no",
|
||||||
|
"rm_item_code",
|
||||||
|
"reference_name",
|
||||||
|
"batch_no",
|
||||||
|
"consumed_qty",
|
||||||
|
"main_item_code",
|
||||||
|
"parent as voucher_no",
|
||||||
|
]
|
||||||
|
|
||||||
|
if self.subcontract_data.receipt_supplied_items_field != "Purchase Receipt Item Supplied":
|
||||||
|
fields.append("serial_and_batch_bundle")
|
||||||
|
|
||||||
return frappe.get_all(
|
return frappe.get_all(
|
||||||
self.subcontract_data.receipt_supplied_items_field,
|
self.subcontract_data.receipt_supplied_items_field,
|
||||||
fields=[
|
fields=fields,
|
||||||
"serial_no",
|
|
||||||
"rm_item_code",
|
|
||||||
"reference_name",
|
|
||||||
"serial_and_batch_bundle",
|
|
||||||
"batch_no",
|
|
||||||
"consumed_qty",
|
|
||||||
"main_item_code",
|
|
||||||
"parent as voucher_no",
|
|
||||||
],
|
|
||||||
filters={"docstatus": 1, "reference_name": ("in", list(receipt_items)), "parenttype": doctype},
|
filters={"docstatus": 1, "reference_name": ("in", list(receipt_items)), "parenttype": doctype},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import frappe
|
|||||||
from frappe import _, scrub
|
from frappe import _, scrub
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
|
from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
|
||||||
|
from frappe.utils.deprecations import deprecated
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate
|
from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate
|
||||||
@ -74,7 +75,7 @@ class calculate_taxes_and_totals(object):
|
|||||||
self.calculate_net_total()
|
self.calculate_net_total()
|
||||||
self.calculate_tax_withholding_net_total()
|
self.calculate_tax_withholding_net_total()
|
||||||
self.calculate_taxes()
|
self.calculate_taxes()
|
||||||
self.manipulate_grand_total_for_inclusive_tax()
|
self.adjust_grand_total_for_inclusive_tax()
|
||||||
self.calculate_totals()
|
self.calculate_totals()
|
||||||
self._cleanup()
|
self._cleanup()
|
||||||
self.calculate_total_net_weight()
|
self.calculate_total_net_weight()
|
||||||
@ -97,6 +98,7 @@ class calculate_taxes_and_totals(object):
|
|||||||
item_doc = frappe.get_cached_doc("Item", item.item_code)
|
item_doc = frappe.get_cached_doc("Item", item.item_code)
|
||||||
args = {
|
args = {
|
||||||
"net_rate": item.net_rate or item.rate,
|
"net_rate": item.net_rate or item.rate,
|
||||||
|
"base_net_rate": item.base_net_rate or item.base_rate,
|
||||||
"tax_category": self.doc.get("tax_category"),
|
"tax_category": self.doc.get("tax_category"),
|
||||||
"posting_date": self.doc.get("posting_date"),
|
"posting_date": self.doc.get("posting_date"),
|
||||||
"bill_date": self.doc.get("bill_date"),
|
"bill_date": self.doc.get("bill_date"),
|
||||||
@ -279,7 +281,7 @@ class calculate_taxes_and_totals(object):
|
|||||||
):
|
):
|
||||||
amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
|
amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
|
||||||
|
|
||||||
item.net_amount = flt(amount / (1 + cumulated_tax_fraction))
|
item.net_amount = flt(amount / (1 + cumulated_tax_fraction), item.precision("net_amount"))
|
||||||
item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
|
item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
|
||||||
item.discount_percentage = flt(item.discount_percentage, item.precision("discount_percentage"))
|
item.discount_percentage = flt(item.discount_percentage, item.precision("discount_percentage"))
|
||||||
|
|
||||||
@ -516,7 +518,12 @@ class calculate_taxes_and_totals(object):
|
|||||||
tax.base_tax_amount = round(tax.base_tax_amount, 0)
|
tax.base_tax_amount = round(tax.base_tax_amount, 0)
|
||||||
tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0)
|
tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0)
|
||||||
|
|
||||||
|
@deprecated
|
||||||
def manipulate_grand_total_for_inclusive_tax(self):
|
def manipulate_grand_total_for_inclusive_tax(self):
|
||||||
|
# for backward compatablility - if in case used by an external application
|
||||||
|
return self.adjust_grand_total_for_inclusive_tax()
|
||||||
|
|
||||||
|
def adjust_grand_total_for_inclusive_tax(self):
|
||||||
# if fully inclusive taxes and diff
|
# if fully inclusive taxes and diff
|
||||||
if self.doc.get("taxes") and any(cint(t.included_in_print_rate) for t in self.doc.get("taxes")):
|
if self.doc.get("taxes") and any(cint(t.included_in_print_rate) for t in self.doc.get("taxes")):
|
||||||
last_tax = self.doc.get("taxes")[-1]
|
last_tax = self.doc.get("taxes")[-1]
|
||||||
@ -538,17 +545,21 @@ class calculate_taxes_and_totals(object):
|
|||||||
diff = flt(diff, self.doc.precision("rounding_adjustment"))
|
diff = flt(diff, self.doc.precision("rounding_adjustment"))
|
||||||
|
|
||||||
if diff and abs(diff) <= (5.0 / 10 ** last_tax.precision("tax_amount")):
|
if diff and abs(diff) <= (5.0 / 10 ** last_tax.precision("tax_amount")):
|
||||||
self.doc.rounding_adjustment = diff
|
self.doc.grand_total_diff = diff
|
||||||
|
else:
|
||||||
|
self.doc.grand_total_diff = 0
|
||||||
|
|
||||||
def calculate_totals(self):
|
def calculate_totals(self):
|
||||||
if self.doc.get("taxes"):
|
if self.doc.get("taxes"):
|
||||||
self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment)
|
self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(
|
||||||
|
self.doc.get("grand_total_diff")
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.doc.grand_total = flt(self.doc.net_total)
|
self.doc.grand_total = flt(self.doc.net_total)
|
||||||
|
|
||||||
if self.doc.get("taxes"):
|
if self.doc.get("taxes"):
|
||||||
self.doc.total_taxes_and_charges = flt(
|
self.doc.total_taxes_and_charges = flt(
|
||||||
self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment),
|
self.doc.grand_total - self.doc.net_total - flt(self.doc.get("grand_total_diff")),
|
||||||
self.doc.precision("total_taxes_and_charges"),
|
self.doc.precision("total_taxes_and_charges"),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -613,8 +624,8 @@ class calculate_taxes_and_totals(object):
|
|||||||
self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total")
|
self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total")
|
||||||
)
|
)
|
||||||
|
|
||||||
# if print_in_rate is set, we would have already calculated rounding adjustment
|
# rounding adjustment should always be the difference vetween grand and rounded total
|
||||||
self.doc.rounding_adjustment += flt(
|
self.doc.rounding_adjustment = flt(
|
||||||
self.doc.rounded_total - self.doc.grand_total, self.doc.precision("rounding_adjustment")
|
self.doc.rounded_total - self.doc.grand_total, self.doc.precision("rounding_adjustment")
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -832,7 +843,6 @@ class calculate_taxes_and_totals(object):
|
|||||||
self.calculate_paid_amount()
|
self.calculate_paid_amount()
|
||||||
|
|
||||||
def calculate_paid_amount(self):
|
def calculate_paid_amount(self):
|
||||||
|
|
||||||
paid_amount = base_paid_amount = 0.0
|
paid_amount = base_paid_amount = 0.0
|
||||||
|
|
||||||
if self.doc.is_pos:
|
if self.doc.is_pos:
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -176,8 +176,10 @@ class BOM(WebsiteGenerator):
|
|||||||
|
|
||||||
def autoname(self):
|
def autoname(self):
|
||||||
# ignore amended documents while calculating current index
|
# ignore amended documents while calculating current index
|
||||||
|
|
||||||
|
search_key = f"{self.doctype}-{self.item}%"
|
||||||
existing_boms = frappe.get_all(
|
existing_boms = frappe.get_all(
|
||||||
"BOM", filters={"item": self.item, "amended_from": ["is", "not set"]}, pluck="name"
|
"BOM", filters={"name": ("like", search_key), "amended_from": ["is", "not set"]}, pluck="name"
|
||||||
)
|
)
|
||||||
|
|
||||||
if existing_boms:
|
if existing_boms:
|
||||||
|
@ -955,6 +955,14 @@ class JobCard(Document):
|
|||||||
if update_status:
|
if update_status:
|
||||||
self.db_set("status", self.status)
|
self.db_set("status", self.status)
|
||||||
|
|
||||||
|
if self.status in ["Completed", "Work In Progress"]:
|
||||||
|
status = {
|
||||||
|
"Completed": "Off",
|
||||||
|
"Work In Progress": "Production",
|
||||||
|
}.get(self.status)
|
||||||
|
|
||||||
|
self.update_status_in_workstation(status)
|
||||||
|
|
||||||
def set_wip_warehouse(self):
|
def set_wip_warehouse(self):
|
||||||
if not self.wip_warehouse:
|
if not self.wip_warehouse:
|
||||||
self.wip_warehouse = frappe.db.get_single_value(
|
self.wip_warehouse = frappe.db.get_single_value(
|
||||||
@ -1035,6 +1043,12 @@ class JobCard(Document):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def update_status_in_workstation(self, status):
|
||||||
|
if not self.workstation:
|
||||||
|
return
|
||||||
|
|
||||||
|
frappe.db.set_value("Workstation", self.workstation, "status", status)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_time_log(args):
|
def make_time_log(args):
|
||||||
|
256
erpnext/manufacturing/doctype/plant_floor/plant_floor.js
Normal file
256
erpnext/manufacturing/doctype/plant_floor/plant_floor.js
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on("Plant Floor", {
|
||||||
|
setup(frm) {
|
||||||
|
frm.trigger("setup_queries");
|
||||||
|
},
|
||||||
|
|
||||||
|
setup_queries(frm) {
|
||||||
|
frm.set_query("warehouse", (doc) => {
|
||||||
|
if (!doc.company) {
|
||||||
|
frappe.throw(__("Please select Company first"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"is_group": 0,
|
||||||
|
"company": doc.company
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh(frm) {
|
||||||
|
frm.trigger('prepare_stock_dashboard')
|
||||||
|
frm.trigger('prepare_workstation_dashboard')
|
||||||
|
},
|
||||||
|
|
||||||
|
prepare_workstation_dashboard(frm) {
|
||||||
|
let wrapper = $(frm.fields_dict["plant_dashboard"].wrapper);
|
||||||
|
wrapper.empty();
|
||||||
|
|
||||||
|
frappe.visual_plant_floor = new frappe.ui.VisualPlantFloor({
|
||||||
|
wrapper: wrapper,
|
||||||
|
skip_filters: true,
|
||||||
|
plant_floor: frm.doc.name,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
prepare_stock_dashboard(frm) {
|
||||||
|
if (!frm.doc.warehouse) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let wrapper = $(frm.fields_dict["stock_summary"].wrapper);
|
||||||
|
wrapper.empty();
|
||||||
|
|
||||||
|
frappe.visual_stock = new VisualStock({
|
||||||
|
wrapper: wrapper,
|
||||||
|
frm: frm,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
class VisualStock {
|
||||||
|
constructor(opts) {
|
||||||
|
Object.assign(this, opts);
|
||||||
|
this.make();
|
||||||
|
}
|
||||||
|
|
||||||
|
make() {
|
||||||
|
this.prepare_filters();
|
||||||
|
this.prepare_stock_summary({
|
||||||
|
start:0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_filters() {
|
||||||
|
this.wrapper.append(`
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 filter-section section-body">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
this.item_filter = frappe.ui.form.make_control({
|
||||||
|
df: {
|
||||||
|
fieldtype: "Link",
|
||||||
|
fieldname: "item_code",
|
||||||
|
placeholder: __("Item"),
|
||||||
|
options: "Item",
|
||||||
|
onchange: () => this.prepare_stock_summary({
|
||||||
|
start:0,
|
||||||
|
item_code: this.item_filter.value
|
||||||
|
})
|
||||||
|
},
|
||||||
|
parent: this.wrapper.find('.filter-section'),
|
||||||
|
render_input: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.item_filter.$wrapper.addClass('form-column col-sm-3');
|
||||||
|
this.item_filter.$wrapper.find('.clearfix').hide();
|
||||||
|
|
||||||
|
this.item_group_filter = frappe.ui.form.make_control({
|
||||||
|
df: {
|
||||||
|
fieldtype: "Link",
|
||||||
|
fieldname: "item_group",
|
||||||
|
placeholder: __("Item Group"),
|
||||||
|
options: "Item Group",
|
||||||
|
change: () => this.prepare_stock_summary({
|
||||||
|
start:0,
|
||||||
|
item_group: this.item_group_filter.value
|
||||||
|
})
|
||||||
|
},
|
||||||
|
parent: this.wrapper.find('.filter-section'),
|
||||||
|
render_input: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.item_group_filter.$wrapper.addClass('form-column col-sm-3');
|
||||||
|
this.item_group_filter.$wrapper.find('.clearfix').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_stock_summary(args) {
|
||||||
|
let {start, item_code, item_group} = args;
|
||||||
|
|
||||||
|
this.get_stock_summary(start, item_code, item_group).then(stock_summary => {
|
||||||
|
this.wrapper.find('.stock-summary-container').remove();
|
||||||
|
this.wrapper.append(`<div class="col-sm-12 stock-summary-container" style="margin-bottom:20px"></div>`);
|
||||||
|
this.stock_summary = stock_summary.message;
|
||||||
|
this.render_stock_summary();
|
||||||
|
this.bind_events();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async get_stock_summary(start, item_code, item_group) {
|
||||||
|
let stock_summary = await frappe.call({
|
||||||
|
method: "erpnext.manufacturing.doctype.plant_floor.plant_floor.get_stock_summary",
|
||||||
|
args: {
|
||||||
|
warehouse: this.frm.doc.warehouse,
|
||||||
|
start: start,
|
||||||
|
item_code: item_code,
|
||||||
|
item_group: item_group
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return stock_summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
render_stock_summary() {
|
||||||
|
let template = frappe.render_template("stock_summary_template", {
|
||||||
|
stock_summary: this.stock_summary
|
||||||
|
});
|
||||||
|
|
||||||
|
this.wrapper.find('.stock-summary-container').append(template);
|
||||||
|
}
|
||||||
|
|
||||||
|
bind_events() {
|
||||||
|
this.wrapper.find('.btn-add').click((e) => {
|
||||||
|
this.item_code = decodeURI($(e.currentTarget).attr('data-item-code'));
|
||||||
|
|
||||||
|
this.make_stock_entry([
|
||||||
|
{
|
||||||
|
label: __("For Item"),
|
||||||
|
fieldname: "item_code",
|
||||||
|
fieldtype: "Data",
|
||||||
|
read_only: 1,
|
||||||
|
default: this.item_code
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("Quantity"),
|
||||||
|
fieldname: "qty",
|
||||||
|
fieldtype: "Float",
|
||||||
|
reqd: 1
|
||||||
|
}
|
||||||
|
], __("Add Stock"), "Material Receipt")
|
||||||
|
});
|
||||||
|
|
||||||
|
this.wrapper.find('.btn-move').click((e) => {
|
||||||
|
this.item_code = decodeURI($(e.currentTarget).attr('data-item-code'));
|
||||||
|
|
||||||
|
this.make_stock_entry([
|
||||||
|
{
|
||||||
|
label: __("For Item"),
|
||||||
|
fieldname: "item_code",
|
||||||
|
fieldtype: "Data",
|
||||||
|
read_only: 1,
|
||||||
|
default: this.item_code
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("Quantity"),
|
||||||
|
fieldname: "qty",
|
||||||
|
fieldtype: "Float",
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("To Warehouse"),
|
||||||
|
fieldname: "to_warehouse",
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Warehouse",
|
||||||
|
reqd: 1,
|
||||||
|
get_query: () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"is_group": 0,
|
||||||
|
"company": this.frm.doc.company
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
], __("Move Stock"), "Material Transfer")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
make_stock_entry(fields, title, stock_entry_type) {
|
||||||
|
frappe.prompt(fields,
|
||||||
|
(values) => {
|
||||||
|
this.values = values;
|
||||||
|
this.stock_entry_type = stock_entry_type;
|
||||||
|
this.update_values();
|
||||||
|
|
||||||
|
this.frm.call({
|
||||||
|
method: "make_stock_entry",
|
||||||
|
doc: this.frm.doc,
|
||||||
|
args: {
|
||||||
|
kwargs: this.values,
|
||||||
|
},
|
||||||
|
callback: (r) => {
|
||||||
|
if (!r.exc) {
|
||||||
|
var doc = frappe.model.sync(r.message);
|
||||||
|
frappe.set_route("Form", r.message.doctype, r.message.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, __(title), __("Create")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
update_values() {
|
||||||
|
if (!this.values.qty) {
|
||||||
|
frappe.throw(__("Quantity is required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let from_warehouse = "";
|
||||||
|
let to_warehouse = "";
|
||||||
|
|
||||||
|
if (this.stock_entry_type == "Material Receipt") {
|
||||||
|
to_warehouse = this.frm.doc.warehouse;
|
||||||
|
} else {
|
||||||
|
from_warehouse = this.frm.doc.warehouse;
|
||||||
|
to_warehouse = this.values.to_warehouse;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.values = {
|
||||||
|
...this.values,
|
||||||
|
...{
|
||||||
|
"company": this.frm.doc.company,
|
||||||
|
"item_code": this.item_code,
|
||||||
|
"from_warehouse": from_warehouse,
|
||||||
|
"to_warehouse": to_warehouse,
|
||||||
|
"purpose": this.stock_entry_type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
97
erpnext/manufacturing/doctype/plant_floor/plant_floor.json
Normal file
97
erpnext/manufacturing/doctype/plant_floor/plant_floor.json
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "field:floor_name",
|
||||||
|
"creation": "2023-10-06 15:06:07.976066",
|
||||||
|
"default_view": "List",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"workstations_tab",
|
||||||
|
"plant_dashboard",
|
||||||
|
"stock_summary_tab",
|
||||||
|
"stock_summary",
|
||||||
|
"details_tab",
|
||||||
|
"column_break_mvbx",
|
||||||
|
"floor_name",
|
||||||
|
"company",
|
||||||
|
"warehouse"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "floor_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Floor Name",
|
||||||
|
"unique": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:!doc.__islocal",
|
||||||
|
"fieldname": "workstations_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Workstations"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "plant_dashboard",
|
||||||
|
"fieldtype": "HTML",
|
||||||
|
"label": "Plant Dashboard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "details_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Floor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_mvbx",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Warehouse",
|
||||||
|
"options": "Warehouse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:!doc.__islocal && doc.warehouse",
|
||||||
|
"fieldname": "stock_summary_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Stock Summary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "stock_summary",
|
||||||
|
"fieldtype": "HTML",
|
||||||
|
"label": "Stock Summary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2024-01-30 11:59:07.508535",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Manufacturing",
|
||||||
|
"name": "Plant Floor",
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
129
erpnext/manufacturing/doctype/plant_floor/plant_floor.py
Normal file
129
erpnext/manufacturing/doctype/plant_floor/plant_floor.py
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe.query_builder import Order
|
||||||
|
from frappe.utils import get_link_to_form, nowdate, nowtime
|
||||||
|
|
||||||
|
|
||||||
|
class PlantFloor(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 | None
|
||||||
|
floor_name: DF.Data | None
|
||||||
|
warehouse: DF.Link | None
|
||||||
|
# end: auto-generated types
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def make_stock_entry(self, kwargs):
|
||||||
|
if isinstance(kwargs, str):
|
||||||
|
kwargs = frappe.parse_json(kwargs)
|
||||||
|
|
||||||
|
if isinstance(kwargs, dict):
|
||||||
|
kwargs = frappe._dict(kwargs)
|
||||||
|
|
||||||
|
stock_entry = frappe.new_doc("Stock Entry")
|
||||||
|
stock_entry.update(
|
||||||
|
{
|
||||||
|
"company": kwargs.company,
|
||||||
|
"from_warehouse": kwargs.from_warehouse,
|
||||||
|
"to_warehouse": kwargs.to_warehouse,
|
||||||
|
"purpose": kwargs.purpose,
|
||||||
|
"stock_entry_type": kwargs.purpose,
|
||||||
|
"posting_date": nowdate(),
|
||||||
|
"posting_time": nowtime(),
|
||||||
|
"items": self.get_item_details(kwargs),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_entry.set_missing_values()
|
||||||
|
|
||||||
|
return stock_entry
|
||||||
|
|
||||||
|
def get_item_details(self, kwargs) -> list[dict]:
|
||||||
|
item_details = frappe.db.get_value(
|
||||||
|
"Item", kwargs.item_code, ["item_name", "stock_uom", "item_group", "description"], as_dict=True
|
||||||
|
)
|
||||||
|
item_details.update(
|
||||||
|
{
|
||||||
|
"qty": kwargs.qty,
|
||||||
|
"uom": item_details.stock_uom,
|
||||||
|
"item_code": kwargs.item_code,
|
||||||
|
"conversion_factor": 1,
|
||||||
|
"s_warehouse": kwargs.from_warehouse,
|
||||||
|
"t_warehouse": kwargs.to_warehouse,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return [item_details]
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_stock_summary(warehouse, start=0, item_code=None, item_group=None):
|
||||||
|
stock_details = get_stock_details(
|
||||||
|
warehouse, start=start, item_code=item_code, item_group=item_group
|
||||||
|
)
|
||||||
|
|
||||||
|
max_count = 0.0
|
||||||
|
for d in stock_details:
|
||||||
|
d.actual_or_pending = (
|
||||||
|
d.projected_qty
|
||||||
|
+ d.reserved_qty
|
||||||
|
+ d.reserved_qty_for_production
|
||||||
|
+ d.reserved_qty_for_sub_contract
|
||||||
|
)
|
||||||
|
d.pending_qty = 0
|
||||||
|
d.total_reserved = (
|
||||||
|
d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract
|
||||||
|
)
|
||||||
|
if d.actual_or_pending > d.actual_qty:
|
||||||
|
d.pending_qty = d.actual_or_pending - d.actual_qty
|
||||||
|
|
||||||
|
d.max_count = max(d.actual_or_pending, d.actual_qty, d.total_reserved, max_count)
|
||||||
|
max_count = d.max_count
|
||||||
|
d.item_link = get_link_to_form("Item", d.item_code)
|
||||||
|
|
||||||
|
return stock_details
|
||||||
|
|
||||||
|
|
||||||
|
def get_stock_details(warehouse, start=0, item_code=None, item_group=None):
|
||||||
|
item_table = frappe.qb.DocType("Item")
|
||||||
|
bin_table = frappe.qb.DocType("Bin")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(bin_table)
|
||||||
|
.inner_join(item_table)
|
||||||
|
.on(bin_table.item_code == item_table.name)
|
||||||
|
.select(
|
||||||
|
bin_table.item_code,
|
||||||
|
bin_table.actual_qty,
|
||||||
|
bin_table.projected_qty,
|
||||||
|
bin_table.reserved_qty,
|
||||||
|
bin_table.reserved_qty_for_production,
|
||||||
|
bin_table.reserved_qty_for_sub_contract,
|
||||||
|
bin_table.reserved_qty_for_production_plan,
|
||||||
|
bin_table.reserved_stock,
|
||||||
|
item_table.item_name,
|
||||||
|
item_table.item_group,
|
||||||
|
item_table.image,
|
||||||
|
)
|
||||||
|
.where(bin_table.warehouse == warehouse)
|
||||||
|
.limit(20)
|
||||||
|
.offset(start)
|
||||||
|
.orderby(bin_table.actual_qty, order=Order.desc)
|
||||||
|
)
|
||||||
|
|
||||||
|
if item_code:
|
||||||
|
query = query.where(bin_table.item_code == item_code)
|
||||||
|
|
||||||
|
if item_group:
|
||||||
|
query = query.where(item_table.item_group == item_group)
|
||||||
|
|
||||||
|
return query.run(as_dict=True)
|
@ -0,0 +1,61 @@
|
|||||||
|
{% $.each(stock_summary, (idx, row) => { %}
|
||||||
|
<div class="row" style="border-bottom:1px solid var(--border-color); padding:4px 5px; margin-top: 3px;margin-bottom: 3px;">
|
||||||
|
<div class="col-sm-1">
|
||||||
|
{% if(row.image) { %}
|
||||||
|
<img style="width:50px;height:50px;" src="{{row.image}}">
|
||||||
|
{% } else { %}
|
||||||
|
<div style="width:50px;height:50px;background-color:var(--control-bg);text-align:center;padding-top:15px">{{frappe.get_abbr(row.item_code, 2)}}</div>
|
||||||
|
{% } %}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
{% if (row.item_code === row.item_name) { %}
|
||||||
|
{{row.item_link}}
|
||||||
|
{% } else { %}
|
||||||
|
{{row.item_link}}
|
||||||
|
<p>
|
||||||
|
{{row.item_name}}
|
||||||
|
</p>
|
||||||
|
{% } %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-1" title="{{ __('Actual Qty') }}">
|
||||||
|
{{ frappe.format(row.actual_qty, { fieldtype: "Float"})}}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-1" title="{{ __('Reserved Stock') }}">
|
||||||
|
{{ frappe.format(row.reserved_stock, { fieldtype: "Float"})}}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4 small">
|
||||||
|
<span class="inline-graph">
|
||||||
|
<span class="inline-graph-half" title="{{ __("Reserved Qty") }}">
|
||||||
|
<span class="inline-graph-count">{{ row.total_reserved }}</span>
|
||||||
|
<span class="inline-graph-bar">
|
||||||
|
<span class="inline-graph-bar-inner"
|
||||||
|
style="width: {{ cint(Math.abs(row.total_reserved)/row.max_count * 100) || 5 }}%">
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="inline-graph-half" title="{{ __("Actual Qty {0} / Waiting Qty {1}", [row.actual_qty, row.pending_qty]) }}">
|
||||||
|
<span class="inline-graph-count">
|
||||||
|
{{ row.actual_qty }} {{ (row.pending_qty > 0) ? ("(" + row.pending_qty+ ")") : "" }}
|
||||||
|
</span>
|
||||||
|
<span class="inline-graph-bar">
|
||||||
|
<span class="inline-graph-bar-inner dark"
|
||||||
|
style="width: {{ cint(row.actual_qty/row.max_count * 100) }}%">
|
||||||
|
</span>
|
||||||
|
{% if row.pending_qty > 0 %}
|
||||||
|
<span class="inline-graph-bar-inner" title="{{ __("Projected Qty") }}"
|
||||||
|
style="width: {{ cint(row.pending_qty/row.max_count * 100) }}%">
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-1">
|
||||||
|
<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-add" data-item-code="{{ escape(row.item_code) }}">Add</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-1">
|
||||||
|
<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-move" data-item-code="{{ escape(row.item_code) }}">Move</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% }); %}
|
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestPlantFloor(FrappeTestCase):
|
||||||
|
pass
|
@ -2,6 +2,28 @@
|
|||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
frappe.ui.form.on("Workstation", {
|
frappe.ui.form.on("Workstation", {
|
||||||
|
set_illustration_image(frm) {
|
||||||
|
let status_image_field = frm.doc.status == "Production" ? frm.doc.on_status_image : frm.doc.off_status_image;
|
||||||
|
if (status_image_field) {
|
||||||
|
frm.sidebar.image_wrapper.find(".sidebar-image").attr("src", status_image_field);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh(frm) {
|
||||||
|
frm.trigger("set_illustration_image");
|
||||||
|
frm.trigger("prepapre_dashboard");
|
||||||
|
},
|
||||||
|
|
||||||
|
prepapre_dashboard(frm) {
|
||||||
|
let $parent = $(frm.fields_dict["workstation_dashboard"].wrapper);
|
||||||
|
$parent.empty();
|
||||||
|
|
||||||
|
let workstation_dashboard = new WorkstationDashboard({
|
||||||
|
wrapper: $parent,
|
||||||
|
frm: frm
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
onload(frm) {
|
onload(frm) {
|
||||||
if(frm.is_new())
|
if(frm.is_new())
|
||||||
{
|
{
|
||||||
@ -54,3 +76,243 @@ frappe.tour['Workstation'] = [
|
|||||||
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
class WorkstationDashboard {
|
||||||
|
constructor({ wrapper, frm }) {
|
||||||
|
this.$wrapper = $(wrapper);
|
||||||
|
this.frm = frm;
|
||||||
|
|
||||||
|
this.prepapre_dashboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
prepapre_dashboard() {
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.manufacturing.doctype.workstation.workstation.get_job_cards",
|
||||||
|
args: {
|
||||||
|
workstation: this.frm.doc.name
|
||||||
|
},
|
||||||
|
callback: (r) => {
|
||||||
|
if (r.message) {
|
||||||
|
this.job_cards = r.message;
|
||||||
|
this.render_job_cards();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render_job_cards() {
|
||||||
|
let template = frappe.render_template("workstation_job_card", {
|
||||||
|
data: this.job_cards
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$wrapper.html(template);
|
||||||
|
this.prepare_timer();
|
||||||
|
this.toggle_job_card();
|
||||||
|
this.bind_events();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle_job_card() {
|
||||||
|
this.$wrapper.find(".collapse-indicator-job").on("click", (e) => {
|
||||||
|
$(e.currentTarget).closest(".form-dashboard-section").find(".section-body-job-card").toggleClass("hide")
|
||||||
|
if ($(e.currentTarget).closest(".form-dashboard-section").find(".section-body-job-card").hasClass("hide"))
|
||||||
|
$(e.currentTarget).html(frappe.utils.icon("es-line-down", "sm", "mb-1"))
|
||||||
|
else
|
||||||
|
$(e.currentTarget).html(frappe.utils.icon("es-line-up", "sm", "mb-1"))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bind_events() {
|
||||||
|
this.$wrapper.find(".make-material-request").on("click", (e) => {
|
||||||
|
let job_card = $(e.currentTarget).attr("job-card");
|
||||||
|
this.make_material_request(job_card);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$wrapper.find(".btn-start").on("click", (e) => {
|
||||||
|
let job_card = $(e.currentTarget).attr("job-card");
|
||||||
|
this.start_job(job_card);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$wrapper.find(".btn-complete").on("click", (e) => {
|
||||||
|
let job_card = $(e.currentTarget).attr("job-card");
|
||||||
|
let pending_qty = flt($(e.currentTarget).attr("pending-qty"));
|
||||||
|
this.complete_job(job_card, pending_qty);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
start_job(job_card) {
|
||||||
|
let me = this;
|
||||||
|
frappe.prompt([
|
||||||
|
{
|
||||||
|
fieldtype: 'Datetime',
|
||||||
|
label: __('Start Time'),
|
||||||
|
fieldname: 'start_time',
|
||||||
|
reqd: 1,
|
||||||
|
default: frappe.datetime.now_datetime()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Operator'),
|
||||||
|
fieldname: 'employee',
|
||||||
|
fieldtype: 'Link',
|
||||||
|
options: 'Employee',
|
||||||
|
}
|
||||||
|
], data => {
|
||||||
|
this.frm.call({
|
||||||
|
method: "start_job",
|
||||||
|
doc: this.frm.doc,
|
||||||
|
args: {
|
||||||
|
job_card: job_card,
|
||||||
|
from_time: data.start_time,
|
||||||
|
employee: data.employee,
|
||||||
|
},
|
||||||
|
callback(r) {
|
||||||
|
if (r.message) {
|
||||||
|
me.job_cards = [r.message];
|
||||||
|
me.prepare_timer()
|
||||||
|
me.update_job_card_details();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, __("Enter Value"), __("Start Job"));
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_job(job_card, qty_to_manufacture) {
|
||||||
|
let me = this;
|
||||||
|
let fields = [
|
||||||
|
{
|
||||||
|
fieldtype: 'Float',
|
||||||
|
label: __('Completed Quantity'),
|
||||||
|
fieldname: 'qty',
|
||||||
|
reqd: 1,
|
||||||
|
default: flt(qty_to_manufacture || 0)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: 'Datetime',
|
||||||
|
label: __('End Time'),
|
||||||
|
fieldname: 'end_time',
|
||||||
|
default: frappe.datetime.now_datetime()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
frappe.prompt(fields, data => {
|
||||||
|
if (data.qty <= 0) {
|
||||||
|
frappe.throw(__("Quantity should be greater than 0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.frm.call({
|
||||||
|
method: "complete_job",
|
||||||
|
doc: this.frm.doc,
|
||||||
|
args: {
|
||||||
|
job_card: job_card,
|
||||||
|
qty: data.qty,
|
||||||
|
to_time: data.end_time,
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
me.job_cards = [r.message];
|
||||||
|
me.prepare_timer()
|
||||||
|
me.update_job_card_details();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, __("Enter Value"), __("Submit"));
|
||||||
|
}
|
||||||
|
|
||||||
|
make_material_request(job_card) {
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.manufacturing.doctype.job_card.job_card.make_material_request",
|
||||||
|
args: {
|
||||||
|
source_name: job_card,
|
||||||
|
},
|
||||||
|
callback: (r) => {
|
||||||
|
if (r.message) {
|
||||||
|
var doc = frappe.model.sync(r.message)[0];
|
||||||
|
frappe.set_route("Form", doc.doctype, doc.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_timer() {
|
||||||
|
this.job_cards.forEach((data) => {
|
||||||
|
if (data.time_logs?.length) {
|
||||||
|
data._current_time = this.get_current_time(data);
|
||||||
|
if (data.time_logs[cint(data.time_logs.length) - 1].to_time) {
|
||||||
|
this.updateStopwatch(data);
|
||||||
|
} else {
|
||||||
|
this.initialiseTimer(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
update_job_card_details() {
|
||||||
|
let color_map = {
|
||||||
|
"Pending": "var(--bg-blue)",
|
||||||
|
"In Process": "var(--bg-yellow)",
|
||||||
|
"Submitted": "var(--bg-blue)",
|
||||||
|
"Open": "var(--bg-gray)",
|
||||||
|
"Closed": "var(--bg-green)",
|
||||||
|
"Work In Progress": "var(--bg-orange)",
|
||||||
|
}
|
||||||
|
|
||||||
|
this.job_cards.forEach((data) => {
|
||||||
|
let job_card_selector = this.$wrapper.find(`
|
||||||
|
[data-name='${data.name}']`
|
||||||
|
);
|
||||||
|
|
||||||
|
$(job_card_selector).find(".job-card-status").text(data.status);
|
||||||
|
$(job_card_selector).find(".job-card-status").css("backgroundColor", color_map[data.status]);
|
||||||
|
|
||||||
|
if (data.status === "Work In Progress") {
|
||||||
|
$(job_card_selector).find(".btn-start").addClass("hide");
|
||||||
|
$(job_card_selector).find(".btn-complete").removeClass("hide");
|
||||||
|
} else if (data.status === "Completed") {
|
||||||
|
$(job_card_selector).find(".btn-start").addClass("hide");
|
||||||
|
$(job_card_selector).find(".btn-complete").addClass("hide");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initialiseTimer(data) {
|
||||||
|
setInterval(() => {
|
||||||
|
data._current_time += 1;
|
||||||
|
this.updateStopwatch(data);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStopwatch(data) {
|
||||||
|
let increment = data._current_time;
|
||||||
|
let hours = Math.floor(increment / 3600);
|
||||||
|
let minutes = Math.floor((increment - (hours * 3600)) / 60);
|
||||||
|
let seconds = cint(increment - (hours * 3600) - (minutes * 60));
|
||||||
|
|
||||||
|
let job_card_selector = `[data-job-card='${data.name}']`
|
||||||
|
let timer_selector = this.$wrapper.find(job_card_selector)
|
||||||
|
|
||||||
|
$(timer_selector).find(".hours").text(hours < 10 ? ("0" + hours.toString()) : hours.toString());
|
||||||
|
$(timer_selector).find(".minutes").text(minutes < 10 ? ("0" + minutes.toString()) : minutes.toString());
|
||||||
|
$(timer_selector).find(".seconds").text(seconds < 10 ? ("0" + seconds.toString()) : seconds.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
get_current_time(data) {
|
||||||
|
let current_time = 0.0;
|
||||||
|
data.time_logs.forEach(d => {
|
||||||
|
if (d.to_time) {
|
||||||
|
if (d.time_in_mins) {
|
||||||
|
current_time += flt(d.time_in_mins, 2) * 60;
|
||||||
|
} else {
|
||||||
|
current_time += this.get_seconds_diff(d.to_time, d.from_time);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current_time += this.get_seconds_diff(frappe.datetime.now_datetime(), d.from_time);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return current_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_seconds_diff(d1, d2) {
|
||||||
|
return moment(d1).diff(d2, "seconds");
|
||||||
|
}
|
||||||
|
}
|
@ -8,10 +8,24 @@
|
|||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
|
"dashboard_tab",
|
||||||
|
"workstation_dashboard",
|
||||||
|
"details_tab",
|
||||||
"workstation_name",
|
"workstation_name",
|
||||||
"production_capacity",
|
|
||||||
"column_break_3",
|
|
||||||
"workstation_type",
|
"workstation_type",
|
||||||
|
"plant_floor",
|
||||||
|
"column_break_3",
|
||||||
|
"production_capacity",
|
||||||
|
"warehouse",
|
||||||
|
"production_capacity_section",
|
||||||
|
"parts_per_hour",
|
||||||
|
"workstation_status_tab",
|
||||||
|
"status",
|
||||||
|
"column_break_glcv",
|
||||||
|
"illustration_section",
|
||||||
|
"on_status_image",
|
||||||
|
"column_break_etmc",
|
||||||
|
"off_status_image",
|
||||||
"over_heads",
|
"over_heads",
|
||||||
"hour_rate_electricity",
|
"hour_rate_electricity",
|
||||||
"hour_rate_consumable",
|
"hour_rate_consumable",
|
||||||
@ -24,7 +38,9 @@
|
|||||||
"description",
|
"description",
|
||||||
"working_hours_section",
|
"working_hours_section",
|
||||||
"holiday_list",
|
"holiday_list",
|
||||||
"working_hours"
|
"working_hours",
|
||||||
|
"total_working_hours",
|
||||||
|
"connections_tab"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -120,9 +136,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "1",
|
"default": "1",
|
||||||
|
"description": "Run parallel job cards in a workstation",
|
||||||
"fieldname": "production_capacity",
|
"fieldname": "production_capacity",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Production Capacity",
|
"label": "Job Capacity",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -145,12 +162,97 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "section_break_11",
|
"fieldname": "section_break_11",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "plant_floor",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Plant Floor",
|
||||||
|
"options": "Plant Floor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "workstation_status_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Workstation Status"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "illustration_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Status Illustration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_etmc",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Status",
|
||||||
|
"options": "Production\nOff\nIdle\nProblem\nMaintenance\nSetup"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_glcv",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "on_status_image",
|
||||||
|
"fieldtype": "Attach Image",
|
||||||
|
"label": "Active Status"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "off_status_image",
|
||||||
|
"fieldtype": "Attach Image",
|
||||||
|
"label": "Inactive Status"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Warehouse",
|
||||||
|
"options": "Warehouse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "production_capacity_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Production Capacity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "parts_per_hour",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Parts Per Hour"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "total_working_hours",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Total Working Hours"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:!doc.__islocal",
|
||||||
|
"fieldname": "dashboard_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Job Cards"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "details_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "connections_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Connections",
|
||||||
|
"show_dashboard": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "workstation_dashboard",
|
||||||
|
"fieldtype": "HTML",
|
||||||
|
"label": "Workstation Dashboard"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-wrench",
|
"icon": "icon-wrench",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
|
"image_field": "on_status_image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-04 17:39:01.549346",
|
"modified": "2023-11-30 12:43:35.808845",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Workstation",
|
"name": "Workstation",
|
||||||
|
@ -11,7 +11,11 @@ from frappe.utils import (
|
|||||||
comma_and,
|
comma_and,
|
||||||
flt,
|
flt,
|
||||||
formatdate,
|
formatdate,
|
||||||
|
get_link_to_form,
|
||||||
|
get_time,
|
||||||
|
get_url_to_form,
|
||||||
getdate,
|
getdate,
|
||||||
|
time_diff_in_hours,
|
||||||
time_diff_in_seconds,
|
time_diff_in_seconds,
|
||||||
to_timedelta,
|
to_timedelta,
|
||||||
)
|
)
|
||||||
@ -60,6 +64,23 @@ class Workstation(Document):
|
|||||||
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()
|
||||||
|
self.set_total_working_hours()
|
||||||
|
|
||||||
|
def set_total_working_hours(self):
|
||||||
|
self.total_working_hours = 0.0
|
||||||
|
for row in self.working_hours:
|
||||||
|
self.validate_working_hours(row)
|
||||||
|
|
||||||
|
if row.start_time and row.end_time:
|
||||||
|
row.hours = flt(time_diff_in_hours(row.end_time, row.start_time), row.precision("hours"))
|
||||||
|
self.total_working_hours += row.hours
|
||||||
|
|
||||||
|
def validate_working_hours(self, row):
|
||||||
|
if not (row.start_time and row.end_time):
|
||||||
|
frappe.throw(_("Row #{0}: Start Time and End Time are required").format(row.idx))
|
||||||
|
|
||||||
|
if get_time(row.start_time) >= get_time(row.end_time):
|
||||||
|
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 = (
|
||||||
@ -143,6 +164,141 @@ class Workstation(Document):
|
|||||||
|
|
||||||
return schedule_date
|
return schedule_date
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def start_job(self, job_card, from_time, employee):
|
||||||
|
doc = frappe.get_doc("Job Card", job_card)
|
||||||
|
doc.append("time_logs", {"from_time": from_time, "employee": employee})
|
||||||
|
doc.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
return doc
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def complete_job(self, job_card, qty, to_time):
|
||||||
|
doc = frappe.get_doc("Job Card", job_card)
|
||||||
|
for row in doc.time_logs:
|
||||||
|
if not row.to_time:
|
||||||
|
row.to_time = to_time
|
||||||
|
row.time_in_mins = time_diff_in_hours(row.to_time, row.from_time) / 60
|
||||||
|
row.completed_qty = qty
|
||||||
|
|
||||||
|
doc.save(ignore_permissions=True)
|
||||||
|
doc.submit()
|
||||||
|
|
||||||
|
return doc
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_job_cards(workstation):
|
||||||
|
if frappe.has_permission("Job Card", "read"):
|
||||||
|
jc_data = frappe.get_all(
|
||||||
|
"Job Card",
|
||||||
|
fields=[
|
||||||
|
"name",
|
||||||
|
"production_item",
|
||||||
|
"work_order",
|
||||||
|
"operation",
|
||||||
|
"total_completed_qty",
|
||||||
|
"for_quantity",
|
||||||
|
"transferred_qty",
|
||||||
|
"status",
|
||||||
|
"expected_start_date",
|
||||||
|
"expected_end_date",
|
||||||
|
"time_required",
|
||||||
|
"wip_warehouse",
|
||||||
|
],
|
||||||
|
filters={
|
||||||
|
"workstation": workstation,
|
||||||
|
"docstatus": ("<", 2),
|
||||||
|
"status": ["not in", ["Completed", "Stopped"]],
|
||||||
|
},
|
||||||
|
order_by="expected_start_date, expected_end_date",
|
||||||
|
)
|
||||||
|
|
||||||
|
job_cards = [row.name for row in jc_data]
|
||||||
|
raw_materials = get_raw_materials(job_cards)
|
||||||
|
time_logs = get_time_logs(job_cards)
|
||||||
|
|
||||||
|
allow_excess_transfer = frappe.db.get_single_value(
|
||||||
|
"Manufacturing Settings", "job_card_excess_transfer"
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in jc_data:
|
||||||
|
row.progress_percent = (
|
||||||
|
flt(row.total_completed_qty / row.for_quantity * 100, 2) if row.for_quantity else 0
|
||||||
|
)
|
||||||
|
row.progress_title = _("Total completed quantity: {0}").format(row.total_completed_qty)
|
||||||
|
row.status_color = get_status_color(row.status)
|
||||||
|
row.job_card_link = get_link_to_form("Job Card", row.name)
|
||||||
|
row.work_order_link = get_link_to_form("Work Order", row.work_order)
|
||||||
|
|
||||||
|
row.raw_materials = raw_materials.get(row.name, [])
|
||||||
|
row.time_logs = time_logs.get(row.name, [])
|
||||||
|
row.make_material_request = False
|
||||||
|
if row.for_quantity > row.transferred_qty or allow_excess_transfer:
|
||||||
|
row.make_material_request = True
|
||||||
|
|
||||||
|
return jc_data
|
||||||
|
|
||||||
|
|
||||||
|
def get_status_color(status):
|
||||||
|
color_map = {
|
||||||
|
"Pending": "var(--bg-blue)",
|
||||||
|
"In Process": "var(--bg-yellow)",
|
||||||
|
"Submitted": "var(--bg-blue)",
|
||||||
|
"Open": "var(--bg-gray)",
|
||||||
|
"Closed": "var(--bg-green)",
|
||||||
|
"Work In Progress": "var(--bg-orange)",
|
||||||
|
}
|
||||||
|
|
||||||
|
return color_map.get(status, "var(--bg-blue)")
|
||||||
|
|
||||||
|
|
||||||
|
def get_raw_materials(job_cards):
|
||||||
|
raw_materials = {}
|
||||||
|
|
||||||
|
data = frappe.get_all(
|
||||||
|
"Job Card Item",
|
||||||
|
fields=[
|
||||||
|
"parent",
|
||||||
|
"item_code",
|
||||||
|
"item_group",
|
||||||
|
"uom",
|
||||||
|
"item_name",
|
||||||
|
"source_warehouse",
|
||||||
|
"required_qty",
|
||||||
|
"transferred_qty",
|
||||||
|
],
|
||||||
|
filters={"parent": ["in", job_cards]},
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in data:
|
||||||
|
raw_materials.setdefault(row.parent, []).append(row)
|
||||||
|
|
||||||
|
return raw_materials
|
||||||
|
|
||||||
|
|
||||||
|
def get_time_logs(job_cards):
|
||||||
|
time_logs = {}
|
||||||
|
|
||||||
|
data = frappe.get_all(
|
||||||
|
"Job Card Time Log",
|
||||||
|
fields=[
|
||||||
|
"parent",
|
||||||
|
"name",
|
||||||
|
"employee",
|
||||||
|
"from_time",
|
||||||
|
"to_time",
|
||||||
|
"time_in_mins",
|
||||||
|
],
|
||||||
|
filters={"parent": ["in", job_cards], "parentfield": "time_logs"},
|
||||||
|
order_by="parent, idx",
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in data:
|
||||||
|
time_logs.setdefault(row.parent, []).append(row)
|
||||||
|
|
||||||
|
return time_logs
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_default_holiday_list():
|
def get_default_holiday_list():
|
||||||
@ -201,3 +357,52 @@ def check_workstation_for_holiday(workstation, from_datetime, to_datetime):
|
|||||||
+ "\n".join(applicable_holidays),
|
+ "\n".join(applicable_holidays),
|
||||||
WorkstationHolidayError,
|
WorkstationHolidayError,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_workstations(**kwargs):
|
||||||
|
kwargs = frappe._dict(kwargs)
|
||||||
|
_workstation = frappe.qb.DocType("Workstation")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(_workstation)
|
||||||
|
.select(
|
||||||
|
_workstation.name,
|
||||||
|
_workstation.description,
|
||||||
|
_workstation.status,
|
||||||
|
_workstation.on_status_image,
|
||||||
|
_workstation.off_status_image,
|
||||||
|
)
|
||||||
|
.orderby(_workstation.workstation_type, _workstation.name)
|
||||||
|
.where(_workstation.plant_floor == kwargs.plant_floor)
|
||||||
|
)
|
||||||
|
|
||||||
|
if kwargs.workstation:
|
||||||
|
query = query.where(_workstation.name == kwargs.workstation)
|
||||||
|
|
||||||
|
if kwargs.workstation_type:
|
||||||
|
query = query.where(_workstation.workstation_type == kwargs.workstation_type)
|
||||||
|
|
||||||
|
if kwargs.workstation_status:
|
||||||
|
query = query.where(_workstation.status == kwargs.workstation_status)
|
||||||
|
|
||||||
|
data = query.run(as_dict=True)
|
||||||
|
|
||||||
|
color_map = {
|
||||||
|
"Production": "var(--green-600)",
|
||||||
|
"Off": "var(--gray-600)",
|
||||||
|
"Idle": "var(--gray-600)",
|
||||||
|
"Problem": "var(--red-600)",
|
||||||
|
"Maintenance": "var(--yellow-600)",
|
||||||
|
"Setup": "var(--blue-600)",
|
||||||
|
}
|
||||||
|
|
||||||
|
for d in data:
|
||||||
|
d.workstation_name = get_link_to_form("Workstation", d.name)
|
||||||
|
d.status_image = d.on_status_image
|
||||||
|
d.background_color = color_map.get(d.status, "var(--red-600)")
|
||||||
|
d.workstation_link = get_url_to_form("Workstation", d.name)
|
||||||
|
if d.status != "Production":
|
||||||
|
d.status_image = d.off_status_image
|
||||||
|
|
||||||
|
return data
|
||||||
|
@ -0,0 +1,125 @@
|
|||||||
|
<style>
|
||||||
|
.job-card-link {
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-head-job-card {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
padding-bottom: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div style = "max-height: 400px; overflow-y: auto;">
|
||||||
|
{% $.each(data, (idx, d) => { %}
|
||||||
|
<div class="row form-dashboard-section job-card-link form-links border-gray-200" data-name="{{d.name}}">
|
||||||
|
<div class="section-head section-head-job-card">
|
||||||
|
{{ d.operation }} - {{ d.production_item }}
|
||||||
|
<span class="ml-2 collapse-indicator-job mb-1" style="">
|
||||||
|
{{frappe.utils.icon("es-line-down", "sm", "mb-1")}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="row form-section" style="width:100%;margin-bottom:10px">
|
||||||
|
<div class="form-column col-sm-3">
|
||||||
|
<div class="frappe-control" title="{{__('Job Card')}}" style="text-decoration:underline">
|
||||||
|
{{ d.job_card_link }}
|
||||||
|
</div>
|
||||||
|
<div class="frappe-control" title="{{__('Work Order')}}" style="text-decoration:underline">
|
||||||
|
{{ d.work_order_link }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-column col-sm-2">
|
||||||
|
<div class="frappe-control timer" title="{{__('Timer')}}" style="text-align:center;font-size:14px;" data-job-card = {{escape(d.name)}}>
|
||||||
|
<span class="hours">00</span>
|
||||||
|
<span class="colon">:</span>
|
||||||
|
<span class="minutes">00</span>
|
||||||
|
<span class="colon">:</span>
|
||||||
|
<span class="seconds">00</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if(d.status === "Open") { %}
|
||||||
|
<div class="frappe-control" title="{{__('Expected Start Date')}}" style="text-align:center;font-size:11px;padding-top: 4px;">
|
||||||
|
{{ frappe.format(d.expected_start_date, { fieldtype: 'Datetime' }) }}
|
||||||
|
</div>
|
||||||
|
{% } else { %}
|
||||||
|
<div class="frappe-control" title="{{__('Expected End Date')}}" style="text-align:center;font-size:11px;padding-top: 4px;">
|
||||||
|
{{ frappe.format(d.expected_end_date, { fieldtype: 'Datetime' }) }}
|
||||||
|
</div>
|
||||||
|
{% } %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="form-column col-sm-2">
|
||||||
|
<div class="frappe-control job-card-status" title="{{__('Status')}}" style="background:{{d.status_color}};text-align:center;border-radius:var(--border-radius-full)">
|
||||||
|
{{ d.status }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-column col-sm-2">
|
||||||
|
<div class="frappe-control" title="{{__('Qty to Manufacture')}}">
|
||||||
|
<div class="progress" title = "{{d.progress_title}}">
|
||||||
|
<div class="progress-bar progress-bar-success" style="width: {{d.progress_percent}}%">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="frappe-control" style="text-align: center; font-size: 10px;">
|
||||||
|
{{ d.for_quantity }} / {{ d.total_completed_qty }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-column col-sm-2 text-center">
|
||||||
|
<button style="width: 85px;" class="btn btn-default btn-start {% if(d.status !== "Open") { %} hide {% } %}" job-card="{{d.name}}"> {{__("Start")}} </button>
|
||||||
|
<button style="width: 85px;" class="btn btn-default btn-complete {% if(d.status === "Open") { %} hide {% } %}" job-card="{{d.name}}" pending-qty="{{d.for_quantity - d.transferred_qty}}"> {{__("Complete")}} </button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-body section-body-job-card form-section hide">
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-column col-sm-2">
|
||||||
|
{{ __("Raw Materials") }}
|
||||||
|
</div>
|
||||||
|
{% if(d.make_material_request) { %}
|
||||||
|
<div class="form-column col-sm-10 text-right">
|
||||||
|
<button class="btn btn-default btn-xs make-material-request" job-card="{{d.name}}">{{ __("Material Request") }}</button>
|
||||||
|
</div>
|
||||||
|
{% } %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if(d.raw_materials) { %}
|
||||||
|
<table class="table table-bordered table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 5%" class="table-sr">Sr</th>
|
||||||
|
|
||||||
|
<th style="width: 15%">{{ __("Item") }}</th>
|
||||||
|
<th style="width: 15%">{{ __("Warehouse") }}</th>
|
||||||
|
<th style="width: 10%">{{__("UOM")}}</th>
|
||||||
|
<th style="width: 15%">{{__("Item Group")}}</th>
|
||||||
|
<th style="width: 20%" >{{__("Required Qty")}}</th>
|
||||||
|
<th style="width: 20%" >{{__("Transferred Qty")}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
{% $.each(d.raw_materials, (row_index, child_row) => { %}
|
||||||
|
<tr>
|
||||||
|
<td class="table-sr">{{ row_index+1 }}</td>
|
||||||
|
{% if(child_row.item_code === child_row.item_name) { %}
|
||||||
|
<td>{{ child_row.item_code }}</td>
|
||||||
|
{% } else { %}
|
||||||
|
<td>{{ child_row.item_code }}: {{child_row.item_name}}</td>
|
||||||
|
{% } %}
|
||||||
|
<td>{{ child_row.source_warehouse }}</td>
|
||||||
|
<td>{{ child_row.uom }}</td>
|
||||||
|
<td>{{ child_row.item_group }}</td>
|
||||||
|
<td>{{ child_row.required_qty }}</td>
|
||||||
|
<td>{{ child_row.transferred_qty }}</td>
|
||||||
|
</tr>
|
||||||
|
{% }); %}
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
{% } %}
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% }); %}
|
||||||
|
</div>
|
@ -1,5 +1,16 @@
|
|||||||
|
|
||||||
frappe.listview_settings['Workstation'] = {
|
frappe.listview_settings['Workstation'] = {
|
||||||
// add_fields: ["status"],
|
add_fields: ["status"],
|
||||||
// filters:[["status","=", "Open"]]
|
get_indicator: function(doc) {
|
||||||
|
let color_map = {
|
||||||
|
"Production": "green",
|
||||||
|
"Off": "gray",
|
||||||
|
"Idle": "gray",
|
||||||
|
"Problem": "red",
|
||||||
|
"Maintenance": "yellow",
|
||||||
|
"Setup": "blue",
|
||||||
|
}
|
||||||
|
|
||||||
|
return [__(doc.status), color_map[doc.status], true];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,150 +1,58 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_import": 0,
|
"creation": "2014-12-24 14:46:40.678236",
|
||||||
"allow_rename": 0,
|
"doctype": "DocType",
|
||||||
"beta": 0,
|
"editable_grid": 1,
|
||||||
"creation": "2014-12-24 14:46:40.678236",
|
"engine": "InnoDB",
|
||||||
"custom": 0,
|
"field_order": [
|
||||||
"docstatus": 0,
|
"start_time",
|
||||||
"doctype": "DocType",
|
"hours",
|
||||||
"document_type": "",
|
"column_break_2",
|
||||||
"editable_grid": 1,
|
"end_time",
|
||||||
"engine": "InnoDB",
|
"enabled"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"fieldname": "start_time",
|
||||||
"bold": 0,
|
"fieldtype": "Time",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "Start Time",
|
||||||
"fieldname": "start_time",
|
"reqd": 1
|
||||||
"fieldtype": "Time",
|
},
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Start Time",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"fieldname": "column_break_2",
|
||||||
"bold": 0,
|
"fieldtype": "Column Break"
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_2",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"fieldname": "end_time",
|
||||||
"bold": 0,
|
"fieldtype": "Time",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "End Time",
|
||||||
"fieldname": "end_time",
|
"reqd": 1
|
||||||
"fieldtype": "Time",
|
},
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "End Time",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"default": "1",
|
||||||
"bold": 0,
|
"fieldname": "enabled",
|
||||||
"collapsible": 0,
|
"fieldtype": "Check",
|
||||||
"columns": 0,
|
"in_list_view": 1,
|
||||||
"default": "1",
|
"label": "Enabled"
|
||||||
"fieldname": "enabled",
|
},
|
||||||
"fieldtype": "Check",
|
{
|
||||||
"hidden": 0,
|
"fieldname": "hours",
|
||||||
"ignore_user_permissions": 0,
|
"fieldtype": "Float",
|
||||||
"ignore_xss_filter": 0,
|
"label": "Hours",
|
||||||
"in_filter": 0,
|
"read_only": 1
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Enabled",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hide_heading": 0,
|
"istable": 1,
|
||||||
"hide_toolbar": 0,
|
"links": [],
|
||||||
"idx": 0,
|
"modified": "2023-10-25 14:48:29.697498",
|
||||||
"image_view": 0,
|
"modified_by": "Administrator",
|
||||||
"in_create": 0,
|
"module": "Manufacturing",
|
||||||
|
"name": "Workstation Working Hour",
|
||||||
"is_submittable": 0,
|
"owner": "Administrator",
|
||||||
"issingle": 0,
|
"permissions": [],
|
||||||
"istable": 1,
|
"sort_field": "modified",
|
||||||
"max_attachments": 0,
|
"sort_order": "DESC",
|
||||||
"modified": "2016-12-13 05:02:36.754145",
|
"states": []
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Manufacturing",
|
|
||||||
"name": "Workstation Working Hour",
|
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"quick_entry": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
@ -1,12 +1,12 @@
|
|||||||
<b>Material Request Type</b>: {{ doc.material_request_type }}<br>
|
<p><b>{{ _("Material Request Type") }}</b>: {{ doc.material_request_type }}<br>
|
||||||
<b>Company</b>: {{ doc.company }}
|
<b>{{ _("Company") }}</b>: {{ doc.company }}</p>
|
||||||
|
|
||||||
<h3>Order Summary</h3>
|
<h3>{{ _("Order Summary") }}</h3>
|
||||||
|
|
||||||
<table border=2 >
|
<table border=2 >
|
||||||
<tr align="center">
|
<tr align="center">
|
||||||
<th>Item Name</th>
|
<th>{{ _("Item Name") }}</th>
|
||||||
<th>Received Quantity</th>
|
<th>{{ _("Received Quantity") }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for item in doc.items %}
|
{% for item in doc.items %}
|
||||||
{% if frappe.utils.flt(item.received_qty, 2) > 0.0 %}
|
{% if frappe.utils.flt(item.received_qty, 2) > 0.0 %}
|
||||||
@ -16,4 +16,4 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
@ -11,19 +11,21 @@
|
|||||||
"event": "Value Change",
|
"event": "Value Change",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"message": "<b>Material Request Type</b>: {{ doc.material_request_type }}<br>\n<b>Company</b>: {{ doc.company }}\n\n<h3>Order Summary</h3>\n\n<table border=2 >\n <tr align=\"center\">\n <th>Item Name</th>\n <th>Received Quantity</th>\n </tr>\n {% for item in doc.items %}\n {% if frappe.utils.flt(item.received_qty, 2) > 0.0 %}\n <tr align=\"center\">\n <td>{{ item.item_code }}</td>\n <td>{{ frappe.utils.flt(item.received_qty, 2) }}</td>\n </tr>\n {% endif %}\n {% endfor %}\n</table>",
|
"message_type": "HTML",
|
||||||
"method": "",
|
"method": "",
|
||||||
"modified": "2019-05-01 18:02:51.090037",
|
"modified": "2023-11-17 08:53:29.525296",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Material Request Receipt Notification",
|
"name": "Material Request Receipt Notification",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"recipients": [
|
"recipients": [
|
||||||
{
|
{
|
||||||
"email_by_document_field": "requested_by"
|
"receiver_by_document_field": "requested_by"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"send_system_notification": 0,
|
||||||
|
"send_to_all_assignees": 0,
|
||||||
"sender_email": "",
|
"sender_email": "",
|
||||||
"subject": "{{ doc.name }} has been received",
|
"subject": "{{ doc.name }} has been received",
|
||||||
"value_changed": "status"
|
"value_changed": "status"
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
|
||||||
|
frappe.pages['visual-plant-floor'].on_page_load = function(wrapper) {
|
||||||
|
var page = frappe.ui.make_app_page({
|
||||||
|
parent: wrapper,
|
||||||
|
title: 'Visual Plant Floor',
|
||||||
|
single_column: true
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.visual_plant_floor = new frappe.ui.VisualPlantFloor(
|
||||||
|
{wrapper: $(wrapper).find('.layout-main-section')}, wrapper.page
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"content": null,
|
||||||
|
"creation": "2023-10-06 15:17:39.215300",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Page",
|
||||||
|
"idx": 0,
|
||||||
|
"modified": "2023-10-06 15:18:00.622073",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Manufacturing",
|
||||||
|
"name": "visual-plant-floor",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"page_name": "visual-plant-floor",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "Manufacturing User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Manufacturing Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Operator"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"script": null,
|
||||||
|
"standard": "Yes",
|
||||||
|
"style": null,
|
||||||
|
"system_page": 0,
|
||||||
|
"title": "Visual Plant Floor"
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"charts": [],
|
"charts": [],
|
||||||
"content": "[{\"id\":\"csBCiDglCE\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"YHCQG3wAGv\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Creator\",\"col\":3}},{\"id\":\"xit0dg7KvY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":3}},{\"id\":\"LRhGV9GAov\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":3}},{\"id\":\"69KKosI6Hg\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":3}},{\"id\":\"PwndxuIpB3\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Job Card\",\"col\":3}},{\"id\":\"OaiDqTT03Y\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":3}},{\"id\":\"OtMcArFRa5\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":3}},{\"id\":\"76yYsI5imF\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":3}},{\"id\":\"PIQJYZOMnD\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Manufacturing\",\"col\":3}},{\"id\":\"bN_6tHS-Ct\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yVEFZMqVwd\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"id\":\"rwrmsTI58-\",\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"id\":\"6dnsyX-siZ\",\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"id\":\"CIq-v5f5KC\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"8RRiQeYr0G\",\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"id\":\"Pu8z7-82rT\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
|
"content": "[{\"id\":\"csBCiDglCE\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"YHCQG3wAGv\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Creator\",\"col\":3}},{\"id\":\"xit0dg7KvY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":3}},{\"id\":\"LRhGV9GAov\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":3}},{\"id\":\"69KKosI6Hg\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":3}},{\"id\":\"PwndxuIpB3\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Job Card\",\"col\":3}},{\"id\":\"Ubj6zXcmIQ\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Plant Floor\",\"col\":3}},{\"id\":\"OtMcArFRa5\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":3}},{\"id\":\"76yYsI5imF\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":3}},{\"id\":\"PIQJYZOMnD\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Manufacturing\",\"col\":3}},{\"id\":\"OaiDqTT03Y\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":3}},{\"id\":\"bN_6tHS-Ct\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yVEFZMqVwd\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"id\":\"rwrmsTI58-\",\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"id\":\"6dnsyX-siZ\",\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"id\":\"CIq-v5f5KC\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"8RRiQeYr0G\",\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"id\":\"Pu8z7-82rT\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
|
||||||
"creation": "2020-03-02 17:11:37.032604",
|
"creation": "2020-03-02 17:11:37.032604",
|
||||||
"custom_blocks": [],
|
"custom_blocks": [],
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
@ -316,7 +316,7 @@
|
|||||||
"type": "Link"
|
"type": "Link"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-08-08 22:28:39.633891",
|
"modified": "2024-01-30 21:49:58.577218",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Manufacturing",
|
"name": "Manufacturing",
|
||||||
@ -336,6 +336,13 @@
|
|||||||
"type": "URL",
|
"type": "URL",
|
||||||
"url": "https://frappe.school/courses/manufacturing?utm_source=in_app"
|
"url": "https://frappe.school/courses/manufacturing?utm_source=in_app"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"color": "Grey",
|
||||||
|
"doc_view": "List",
|
||||||
|
"label": "Plant Floor",
|
||||||
|
"link_to": "Plant Floor",
|
||||||
|
"type": "DocType"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"color": "Grey",
|
"color": "Grey",
|
||||||
"doc_view": "List",
|
"doc_view": "List",
|
||||||
|
@ -160,7 +160,7 @@ erpnext.accounts.taxes = {
|
|||||||
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);
|
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);
|
||||||
@ -170,7 +170,8 @@ erpnext.accounts.taxes = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
validate_inclusive_tax: function(tax) {
|
validate_inclusive_tax: function(tax, 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);
|
||||||
@ -186,12 +187,12 @@ erpnext.accounts.taxes = {
|
|||||||
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" &&
|
} 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") {
|
} 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) { return cint(t.included_in_print_rate) ? null : t; });
|
function(t) { return cint(t.included_in_print_rate) ? null : t; });
|
||||||
if(taxes_not_included.length > 0) {
|
if(taxes_not_included.length > 0) {
|
||||||
|
@ -103,7 +103,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
this.determine_exclusive_rate();
|
this.determine_exclusive_rate();
|
||||||
this.calculate_net_total();
|
this.calculate_net_total();
|
||||||
this.calculate_taxes();
|
this.calculate_taxes();
|
||||||
this.manipulate_grand_total_for_inclusive_tax();
|
this.adjust_grand_total_for_inclusive_tax();
|
||||||
this.calculate_totals();
|
this.calculate_totals();
|
||||||
this._cleanup();
|
this._cleanup();
|
||||||
}
|
}
|
||||||
@ -185,7 +185,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
|
|
||||||
if (!this.discount_amount_applied) {
|
if (!this.discount_amount_applied) {
|
||||||
erpnext.accounts.taxes.validate_taxes_and_charges(tax.doctype, tax.name);
|
erpnext.accounts.taxes.validate_taxes_and_charges(tax.doctype, tax.name);
|
||||||
erpnext.accounts.taxes.validate_inclusive_tax(tax);
|
erpnext.accounts.taxes.validate_inclusive_tax(tax, this.frm);
|
||||||
}
|
}
|
||||||
frappe.model.round_floats_in(tax);
|
frappe.model.round_floats_in(tax);
|
||||||
});
|
});
|
||||||
@ -248,7 +248,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
|
|
||||||
if(!me.discount_amount_applied && item.qty && (total_inclusive_tax_amount_per_qty || cumulated_tax_fraction)) {
|
if(!me.discount_amount_applied && item.qty && (total_inclusive_tax_amount_per_qty || cumulated_tax_fraction)) {
|
||||||
var amount = flt(item.amount) - total_inclusive_tax_amount_per_qty;
|
var amount = flt(item.amount) - total_inclusive_tax_amount_per_qty;
|
||||||
item.net_amount = flt(amount / (1 + cumulated_tax_fraction));
|
item.net_amount = flt(amount / (1 + cumulated_tax_fraction), precision("net_amount", item));
|
||||||
item.net_rate = item.qty ? flt(item.net_amount / item.qty, precision("net_rate", item)) : 0;
|
item.net_rate = item.qty ? flt(item.net_amount / item.qty, precision("net_rate", item)) : 0;
|
||||||
|
|
||||||
me.set_in_company_currency(item, ["net_rate", "net_amount"]);
|
me.set_in_company_currency(item, ["net_rate", "net_amount"]);
|
||||||
@ -303,6 +303,8 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
me.frm.doc.net_total += item.net_amount;
|
me.frm.doc.net_total += item.net_amount;
|
||||||
me.frm.doc.base_net_total += item.base_net_amount;
|
me.frm.doc.base_net_total += item.base_net_amount;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
calculate_shipping_charges() {
|
calculate_shipping_charges() {
|
||||||
@ -521,8 +523,17 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use adjust_grand_total_for_inclusive_tax instead.
|
||||||
|
*/
|
||||||
manipulate_grand_total_for_inclusive_tax() {
|
manipulate_grand_total_for_inclusive_tax() {
|
||||||
|
// for backward compatablility - if in case used by an external application
|
||||||
|
this.adjust_grand_total_for_inclusive_tax()
|
||||||
|
}
|
||||||
|
|
||||||
|
adjust_grand_total_for_inclusive_tax() {
|
||||||
var me = this;
|
var me = this;
|
||||||
|
|
||||||
// if fully inclusive taxes and diff
|
// if fully inclusive taxes and diff
|
||||||
if (this.frm.doc["taxes"] && this.frm.doc["taxes"].length) {
|
if (this.frm.doc["taxes"] && this.frm.doc["taxes"].length) {
|
||||||
var any_inclusive_tax = false;
|
var any_inclusive_tax = false;
|
||||||
@ -548,7 +559,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
diff = flt(diff, precision("rounding_adjustment"));
|
diff = flt(diff, precision("rounding_adjustment"));
|
||||||
|
|
||||||
if ( diff && Math.abs(diff) <= (5.0 / Math.pow(10, precision("tax_amount", last_tax))) ) {
|
if ( diff && Math.abs(diff) <= (5.0 / Math.pow(10, precision("tax_amount", last_tax))) ) {
|
||||||
me.frm.doc.rounding_adjustment = diff;
|
me.frm.doc.grand_total_diff = diff;
|
||||||
|
} else {
|
||||||
|
me.frm.doc.grand_total_diff = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -559,7 +572,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
var me = this;
|
var me = this;
|
||||||
var tax_count = this.frm.doc["taxes"] ? this.frm.doc["taxes"].length : 0;
|
var tax_count = this.frm.doc["taxes"] ? this.frm.doc["taxes"].length : 0;
|
||||||
this.frm.doc.grand_total = flt(tax_count
|
this.frm.doc.grand_total = flt(tax_count
|
||||||
? this.frm.doc["taxes"][tax_count - 1].total + flt(this.frm.doc.rounding_adjustment)
|
? this.frm.doc["taxes"][tax_count - 1].total + flt(this.frm.doc.grand_total_diff)
|
||||||
: this.frm.doc.net_total);
|
: this.frm.doc.net_total);
|
||||||
|
|
||||||
if(in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"], this.frm.doc.doctype)) {
|
if(in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"], this.frm.doc.doctype)) {
|
||||||
@ -619,7 +632,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
if(frappe.meta.get_docfield(this.frm.doc.doctype, "rounded_total", this.frm.doc.name)) {
|
if(frappe.meta.get_docfield(this.frm.doc.doctype, "rounded_total", this.frm.doc.name)) {
|
||||||
this.frm.doc.rounded_total = round_based_on_smallest_currency_fraction(this.frm.doc.grand_total,
|
this.frm.doc.rounded_total = round_based_on_smallest_currency_fraction(this.frm.doc.grand_total,
|
||||||
this.frm.doc.currency, precision("rounded_total"));
|
this.frm.doc.currency, precision("rounded_total"));
|
||||||
this.frm.doc.rounding_adjustment += flt(this.frm.doc.rounded_total - this.frm.doc.grand_total,
|
this.frm.doc.rounding_adjustment = flt(this.frm.doc.rounded_total - this.frm.doc.grand_total,
|
||||||
precision("rounding_adjustment"));
|
precision("rounding_adjustment"));
|
||||||
|
|
||||||
this.set_in_company_currency(this.frm.doc, ["rounding_adjustment", "rounded_total"]);
|
this.set_in_company_currency(this.frm.doc, ["rounding_adjustment", "rounded_total"]);
|
||||||
@ -687,8 +700,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
if (total_for_discount_amount) {
|
if (total_for_discount_amount) {
|
||||||
$.each(this.frm._items || [], function(i, item) {
|
$.each(this.frm._items || [], function(i, item) {
|
||||||
distributed_amount = flt(me.frm.doc.discount_amount) * item.net_amount / total_for_discount_amount;
|
distributed_amount = flt(me.frm.doc.discount_amount) * item.net_amount / total_for_discount_amount;
|
||||||
item.net_amount = flt(item.net_amount - distributed_amount,
|
item.net_amount = flt(item.net_amount - distributed_amount, precision("net_amount", item));
|
||||||
precision("base_amount", item));
|
|
||||||
net_total += item.net_amount;
|
net_total += item.net_amount;
|
||||||
|
|
||||||
// discount amount rounding loss adjustment if no taxes
|
// discount amount rounding loss adjustment if no taxes
|
||||||
|
@ -502,6 +502,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
project: item.project || me.frm.doc.project,
|
project: item.project || me.frm.doc.project,
|
||||||
qty: item.qty || 1,
|
qty: item.qty || 1,
|
||||||
net_rate: item.rate,
|
net_rate: item.rate,
|
||||||
|
base_net_rate: item.base_net_rate,
|
||||||
stock_qty: item.stock_qty,
|
stock_qty: item.stock_qty,
|
||||||
conversion_factor: item.conversion_factor,
|
conversion_factor: item.conversion_factor,
|
||||||
weight_per_unit: item.weight_per_unit,
|
weight_per_unit: item.weight_per_unit,
|
||||||
@ -798,14 +799,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
}
|
}
|
||||||
let selling_doctypes_for_tc = ["Sales Invoice", "Quotation", "Sales Order", "Delivery Note"];
|
let selling_doctypes_for_tc = ["Sales Invoice", "Quotation", "Sales Order", "Delivery Note"];
|
||||||
if (company_doc.default_selling_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
|
if (company_doc.default_selling_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
|
||||||
selling_doctypes_for_tc.indexOf(me.frm.doc.doctype) != -1) {
|
selling_doctypes_for_tc.includes(me.frm.doc.doctype) && !me.frm.doc.tc_name) {
|
||||||
me.frm.set_value("tc_name", company_doc.default_selling_terms);
|
me.frm.set_value("tc_name", company_doc.default_selling_terms);
|
||||||
}
|
}
|
||||||
let buying_doctypes_for_tc = ["Request for Quotation", "Supplier Quotation", "Purchase Order",
|
let buying_doctypes_for_tc = ["Request for Quotation", "Supplier Quotation", "Purchase Order",
|
||||||
"Material Request", "Purchase Receipt"];
|
"Material Request", "Purchase Receipt"];
|
||||||
// Purchase Invoice is excluded as per issue #3345
|
// Purchase Invoice is excluded as per issue #3345
|
||||||
if (company_doc.default_buying_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
|
if (company_doc.default_buying_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
|
||||||
buying_doctypes_for_tc.indexOf(me.frm.doc.doctype) != -1) {
|
buying_doctypes_for_tc.includes(me.frm.doc.doctype) && !me.frm.doc.tc_name) {
|
||||||
me.frm.set_value("tc_name", company_doc.default_buying_terms);
|
me.frm.set_value("tc_name", company_doc.default_buying_terms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1902,7 +1903,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
if (item.item_code) {
|
if (item.item_code) {
|
||||||
// Use combination of name and item code in case same item is added multiple times
|
// Use combination of name and item code in case same item is added multiple times
|
||||||
item_codes.push([item.item_code, item.name]);
|
item_codes.push([item.item_code, item.name]);
|
||||||
item_rates[item.name] = item.net_rate;
|
item_rates[item.name] = item.base_net_rate;
|
||||||
item_tax_templates[item.name] = item.item_tax_template;
|
item_tax_templates[item.name] = item.item_tax_template;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -5,6 +5,8 @@ import "./sms_manager";
|
|||||||
import "./utils/party";
|
import "./utils/party";
|
||||||
import "./controllers/stock_controller";
|
import "./controllers/stock_controller";
|
||||||
import "./payment/payments";
|
import "./payment/payments";
|
||||||
|
import "./templates/visual_plant_floor_template.html";
|
||||||
|
import "./plant_floor_visual/visual_plant";
|
||||||
import "./controllers/taxes_and_totals";
|
import "./controllers/taxes_and_totals";
|
||||||
import "./controllers/transaction";
|
import "./controllers/transaction";
|
||||||
import "./templates/item_selector.html";
|
import "./templates/item_selector.html";
|
||||||
|
@ -2,7 +2,58 @@ frappe.provide("erpnext.financial_statements");
|
|||||||
|
|
||||||
erpnext.financial_statements = {
|
erpnext.financial_statements = {
|
||||||
"filters": get_filters(),
|
"filters": get_filters(),
|
||||||
|
"baseData": null,
|
||||||
"formatter": function(value, row, column, data, default_formatter, filter) {
|
"formatter": function(value, row, column, data, default_formatter, filter) {
|
||||||
|
if(frappe.query_report.get_filter_value("selected_view") == "Growth" && data && column.colIndex >= 3){
|
||||||
|
//Assuming that the first three columns are s.no, account name and the very first year of the accounting values, to calculate the relative percentage values of the successive columns.
|
||||||
|
const lastAnnualValue = row[column.colIndex - 1].content;
|
||||||
|
const currentAnnualvalue = data[column.fieldname];
|
||||||
|
if(currentAnnualvalue == undefined) return 'NA'; //making this not applicable for undefined/null values
|
||||||
|
let annualGrowth = 0;
|
||||||
|
if(lastAnnualValue == 0 && currentAnnualvalue > 0){
|
||||||
|
//If the previous year value is 0 and the current value is greater than 0
|
||||||
|
annualGrowth = 1;
|
||||||
|
}
|
||||||
|
else if(lastAnnualValue > 0){
|
||||||
|
annualGrowth = (currentAnnualvalue - lastAnnualValue) / lastAnnualValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const growthPercent = (Math.round(annualGrowth*10000)/100); //calculating the rounded off percentage
|
||||||
|
|
||||||
|
value = $(`<span>${((growthPercent >=0)? '+':'' )+growthPercent+'%'}</span>`);
|
||||||
|
if(growthPercent < 0){
|
||||||
|
value = $(value).addClass("text-danger");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
value = $(value).addClass("text-success");
|
||||||
|
}
|
||||||
|
value = $(value).wrap("<p></p>").parent().html();
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
else if(frappe.query_report.get_filter_value("selected_view") == "Margin" && data){
|
||||||
|
if(column.fieldname =="account" && data.account_name == __("Income")){
|
||||||
|
//Taking the total income from each column (for all the financial years) as the base (100%)
|
||||||
|
this.baseData = row;
|
||||||
|
}
|
||||||
|
if(column.colIndex >= 2){
|
||||||
|
//Assuming that the first two columns are s.no and account name, to calculate the relative percentage values of the successive columns.
|
||||||
|
const currentAnnualvalue = data[column.fieldname];
|
||||||
|
const baseValue = this.baseData[column.colIndex].content;
|
||||||
|
if(currentAnnualvalue == undefined || baseValue <= 0) return 'NA';
|
||||||
|
const marginPercent = Math.round((currentAnnualvalue/baseValue)*10000)/100;
|
||||||
|
|
||||||
|
value = $(`<span>${marginPercent+'%'}</span>`);
|
||||||
|
if(marginPercent < 0)
|
||||||
|
value = $(value).addClass("text-danger");
|
||||||
|
else
|
||||||
|
value = $(value).addClass("text-success");
|
||||||
|
value = $(value).wrap("<p></p>").parent().html();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if (data && column.fieldname=="account") {
|
if (data && column.fieldname=="account") {
|
||||||
value = data.account_name || value;
|
value = data.account_name || value;
|
||||||
|
|
||||||
@ -74,22 +125,24 @@ erpnext.financial_statements = {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const views_menu = report.page.add_custom_button_group(__('Financial Statements'));
|
if (report.page){
|
||||||
|
const views_menu = report.page.add_custom_button_group(__('Financial Statements'));
|
||||||
|
|
||||||
report.page.add_custom_menu_item(views_menu, __("Balance Sheet"), function() {
|
report.page.add_custom_menu_item(views_menu, __("Balance Sheet"), function() {
|
||||||
var filters = report.get_values();
|
var filters = report.get_values();
|
||||||
frappe.set_route('query-report', 'Balance Sheet', {company: filters.company});
|
frappe.set_route('query-report', 'Balance Sheet', {company: filters.company});
|
||||||
});
|
});
|
||||||
|
|
||||||
report.page.add_custom_menu_item(views_menu, __("Profit and Loss"), function() {
|
report.page.add_custom_menu_item(views_menu, __("Profit and Loss"), function() {
|
||||||
var filters = report.get_values();
|
var filters = report.get_values();
|
||||||
frappe.set_route('query-report', 'Profit and Loss Statement', {company: filters.company});
|
frappe.set_route('query-report', 'Profit and Loss Statement', {company: filters.company});
|
||||||
});
|
});
|
||||||
|
|
||||||
report.page.add_custom_menu_item(views_menu, __("Cash Flow Statement"), function() {
|
report.page.add_custom_menu_item(views_menu, __("Cash Flow Statement"), function() {
|
||||||
var filters = report.get_values();
|
var filters = report.get_values();
|
||||||
frappe.set_route('query-report', 'Cash Flow', {company: filters.company});
|
frappe.set_route('query-report', 'Cash Flow', {company: filters.company});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
157
erpnext/public/js/plant_floor_visual/visual_plant.js
Normal file
157
erpnext/public/js/plant_floor_visual/visual_plant.js
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
class VisualPlantFloor {
|
||||||
|
constructor({wrapper, skip_filters=false, plant_floor=null}, page=null) {
|
||||||
|
this.wrapper = wrapper;
|
||||||
|
this.plant_floor = plant_floor;
|
||||||
|
this.skip_filters = skip_filters;
|
||||||
|
|
||||||
|
this.make();
|
||||||
|
if (!this.skip_filters) {
|
||||||
|
this.page = page;
|
||||||
|
this.add_filter();
|
||||||
|
this.prepare_menu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
make() {
|
||||||
|
this.wrapper.append(`
|
||||||
|
<div class="plant-floor">
|
||||||
|
<div class="plant-floor-filter">
|
||||||
|
</div>
|
||||||
|
<div class="plant-floor-container col-sm-12">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (!this.skip_filters) {
|
||||||
|
this.filter_wrapper = this.wrapper.find('.plant-floor-filter');
|
||||||
|
this.visualization_wrapper = this.wrapper.find('.plant-floor-visualization');
|
||||||
|
} else if(this.plant_floor) {
|
||||||
|
this.wrapper.find('.plant-floor').css('border', 'none');
|
||||||
|
this.prepare_data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_data() {
|
||||||
|
frappe.call({
|
||||||
|
method: 'erpnext.manufacturing.doctype.workstation.workstation.get_workstations',
|
||||||
|
args: {
|
||||||
|
plant_floor: this.plant_floor,
|
||||||
|
},
|
||||||
|
callback: (r) => {
|
||||||
|
this.workstations = r.message;
|
||||||
|
this.render_workstations();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
add_filter() {
|
||||||
|
this.plant_floor = frappe.ui.form.make_control({
|
||||||
|
df: {
|
||||||
|
fieldtype: 'Link',
|
||||||
|
options: 'Plant Floor',
|
||||||
|
fieldname: 'plant_floor',
|
||||||
|
label: __('Plant Floor'),
|
||||||
|
reqd: 1,
|
||||||
|
onchange: () => {
|
||||||
|
this.render_plant_visualization();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parent: this.filter_wrapper,
|
||||||
|
render_input: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.plant_floor.$wrapper.addClass('form-column col-sm-2');
|
||||||
|
|
||||||
|
this.workstation_type = frappe.ui.form.make_control({
|
||||||
|
df: {
|
||||||
|
fieldtype: 'Link',
|
||||||
|
options: 'Workstation Type',
|
||||||
|
fieldname: 'workstation_type',
|
||||||
|
label: __('Machine Type'),
|
||||||
|
onchange: () => {
|
||||||
|
this.render_plant_visualization();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parent: this.filter_wrapper,
|
||||||
|
render_input: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.workstation_type.$wrapper.addClass('form-column col-sm-2');
|
||||||
|
|
||||||
|
this.workstation = frappe.ui.form.make_control({
|
||||||
|
df: {
|
||||||
|
fieldtype: 'Link',
|
||||||
|
options: 'Workstation',
|
||||||
|
fieldname: 'workstation',
|
||||||
|
label: __('Machine'),
|
||||||
|
onchange: () => {
|
||||||
|
this.render_plant_visualization();
|
||||||
|
},
|
||||||
|
get_query: () => {
|
||||||
|
if (this.workstation_type.get_value()) {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'workstation_type': this.workstation_type.get_value() || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parent: this.filter_wrapper,
|
||||||
|
render_input: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.workstation.$wrapper.addClass('form-column col-sm-2');
|
||||||
|
|
||||||
|
this.workstation_status = frappe.ui.form.make_control({
|
||||||
|
df: {
|
||||||
|
fieldtype: 'Select',
|
||||||
|
options: '\nProduction\nOff\nIdle\nProblem\nMaintenance\nSetup',
|
||||||
|
fieldname: 'workstation_status',
|
||||||
|
label: __('Status'),
|
||||||
|
onchange: () => {
|
||||||
|
this.render_plant_visualization();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
parent: this.filter_wrapper,
|
||||||
|
render_input: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render_plant_visualization() {
|
||||||
|
let plant_floor = this.plant_floor.get_value();
|
||||||
|
|
||||||
|
if (plant_floor) {
|
||||||
|
frappe.call({
|
||||||
|
method: 'erpnext.manufacturing.doctype.workstation.workstation.get_workstations',
|
||||||
|
args: {
|
||||||
|
plant_floor: plant_floor,
|
||||||
|
workstation_type: this.workstation_type.get_value(),
|
||||||
|
workstation: this.workstation.get_value(),
|
||||||
|
workstation_status: this.workstation_status.get_value()
|
||||||
|
},
|
||||||
|
callback: (r) => {
|
||||||
|
this.workstations = r.message;
|
||||||
|
this.render_workstations();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render_workstations() {
|
||||||
|
this.wrapper.find('.plant-floor-container').empty();
|
||||||
|
let template = frappe.render_template("visual_plant_floor_template", {
|
||||||
|
workstations: this.workstations
|
||||||
|
});
|
||||||
|
|
||||||
|
$(template).appendTo(this.wrapper.find('.plant-floor-container'));
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_menu() {
|
||||||
|
this.page.add_menu_item(__('Refresh'), () => {
|
||||||
|
this.render_plant_visualization();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.ui.VisualPlantFloor = VisualPlantFloor;
|
19
erpnext/public/js/templates/visual_plant_floor_template.html
Normal file
19
erpnext/public/js/templates/visual_plant_floor_template.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{% $.each(workstations, (idx, row) => { %}
|
||||||
|
<div class="workstation-wrapper">
|
||||||
|
<div class="workstation-image">
|
||||||
|
<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
|
||||||
|
<a class="workstation-image-link" href="{{row.workstation_link}}">
|
||||||
|
{% if(row.status_image) { %}
|
||||||
|
<img class="workstation-image-cls" src="{{row.status_image}}">
|
||||||
|
{% } else { %}
|
||||||
|
<div class="workstation-image-cls workstation-abbr">{{frappe.get_abbr(row.name, 2)}}</div>
|
||||||
|
{% } %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="workstation-card text-center">
|
||||||
|
<p style="background-color:{{row.background_color}};color:#fff">{{row.status}}</p>
|
||||||
|
<div>{{row.workstation_name}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% }); %}
|
@ -490,3 +490,53 @@ body[data-route="pos"] {
|
|||||||
.exercise-col {
|
.exercise-col {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.plant-floor, .workstation-wrapper, .workstation-card p {
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: none;
|
||||||
|
background-color: var(--card-bg);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-floor {
|
||||||
|
padding-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-floor-filter {
|
||||||
|
padding-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-floor-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(6,minmax(0,1fr));
|
||||||
|
gap: var(--margin-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 620px) {
|
||||||
|
.plant-floor-container {
|
||||||
|
grid-template-columns: repeat(2,minmax(0,1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-floor-container .workstation-card {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-floor-container .workstation-image-link {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 50px;
|
||||||
|
margin: var(--margin-sm);
|
||||||
|
min-height: 9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workstation-abbr {
|
||||||
|
display: flex;
|
||||||
|
background-color: var(--control-bg);
|
||||||
|
height:100%;
|
||||||
|
width:100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
"actions": [],
|
"actions": [],
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"creation": "2013-06-20 11:53:21",
|
"creation": "2013-06-20 11:53:21",
|
||||||
"description": "Aggregate group of **Items** into another **Item**. This is useful if you are bundling a certain **Items** into a package and you maintain stock of the packed **Items** and not the aggregate **Item**. \n\nThe package **Item** will have \"Is Stock Item\" as \"No\" and \"Is Sales Item\" as \"Yes\".\n\nFor Example: If you are selling Laptops and Backpacks separately and have a special price if the customer buys both, then the Laptop + Backpack will be a new Product Bundle Item.\n\nNote: BOM = Bill of Materials",
|
"description": "Aggregate a group of Items into another Item. This is useful if you are maintaining the stock of the packed items and not the bundled item",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
@ -77,7 +77,7 @@
|
|||||||
"icon": "fa fa-sitemap",
|
"icon": "fa fa-sitemap",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-22 15:20:46.805114",
|
"modified": "2024-01-30 13:57:04.951788",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Product Bundle",
|
"name": "Product Bundle",
|
||||||
|
@ -391,7 +391,6 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
|
|||||||
balance_qty = obj.qty - ordered_items.get(obj.item_code, 0.0)
|
balance_qty = obj.qty - ordered_items.get(obj.item_code, 0.0)
|
||||||
target.qty = balance_qty if balance_qty > 0 else 0
|
target.qty = balance_qty if balance_qty > 0 else 0
|
||||||
target.stock_qty = flt(target.qty) * flt(obj.conversion_factor)
|
target.stock_qty = flt(target.qty) * flt(obj.conversion_factor)
|
||||||
target.delivery_date = nowdate()
|
|
||||||
|
|
||||||
if obj.against_blanket_order:
|
if obj.against_blanket_order:
|
||||||
target.against_blanket_order = obj.against_blanket_order
|
target.against_blanket_order = obj.against_blanket_order
|
||||||
|
@ -99,7 +99,6 @@ class TestQuotation(FrappeTestCase):
|
|||||||
self.assertEqual(sales_order.get("items")[0].prevdoc_docname, quotation.name)
|
self.assertEqual(sales_order.get("items")[0].prevdoc_docname, quotation.name)
|
||||||
self.assertEqual(sales_order.customer, "_Test Customer")
|
self.assertEqual(sales_order.customer, "_Test Customer")
|
||||||
|
|
||||||
sales_order.delivery_date = "2014-01-01"
|
|
||||||
sales_order.naming_series = "_T-Quotation-"
|
sales_order.naming_series = "_T-Quotation-"
|
||||||
sales_order.transaction_date = nowdate()
|
sales_order.transaction_date = nowdate()
|
||||||
sales_order.insert()
|
sales_order.insert()
|
||||||
@ -132,7 +131,6 @@ class TestQuotation(FrappeTestCase):
|
|||||||
self.assertEqual(sales_order.get("items")[0].prevdoc_docname, quotation.name)
|
self.assertEqual(sales_order.get("items")[0].prevdoc_docname, quotation.name)
|
||||||
self.assertEqual(sales_order.customer, "_Test Customer")
|
self.assertEqual(sales_order.customer, "_Test Customer")
|
||||||
|
|
||||||
sales_order.delivery_date = "2014-01-01"
|
|
||||||
sales_order.naming_series = "_T-Quotation-"
|
sales_order.naming_series = "_T-Quotation-"
|
||||||
sales_order.transaction_date = nowdate()
|
sales_order.transaction_date = nowdate()
|
||||||
sales_order.insert()
|
sales_order.insert()
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:item_group_name",
|
"autoname": "field:item_group_name",
|
||||||
"creation": "2013-03-28 10:35:29",
|
"creation": "2013-03-28 10:35:29",
|
||||||
"description": "Item Classification",
|
"description": "An Item Group is a way to classify items based on types.",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
@ -135,7 +135,7 @@
|
|||||||
"is_tree": 1,
|
"is_tree": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"max_attachments": 3,
|
"max_attachments": 3,
|
||||||
"modified": "2023-10-12 13:44:13.611287",
|
"modified": "2024-01-30 14:08:38.485616",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Item Group",
|
"name": "Item Group",
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:sales_person_name",
|
"autoname": "field:sales_person_name",
|
||||||
"creation": "2013-01-10 16:34:24",
|
"creation": "2013-01-10 16:34:24",
|
||||||
"description": "All Sales Transactions can be tagged against multiple **Sales Persons** so that you can set and monitor targets.",
|
"description": "All Sales Transactions can be tagged against multiple Sales Persons so that you can set and monitor targets.",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
@ -145,10 +145,11 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"is_tree": 1,
|
"is_tree": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-03-18 18:11:13.968024",
|
"modified": "2024-01-30 13:57:26.436991",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Sales Person",
|
"name": "Sales Person",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
"nsm_parent_field": "parent_sales_person",
|
"nsm_parent_field": "parent_sales_person",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
@ -181,5 +182,6 @@
|
|||||||
"search_fields": "parent_sales_person",
|
"search_fields": "parent_sales_person",
|
||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "ASC"
|
"sort_order": "ASC",
|
||||||
|
"states": []
|
||||||
}
|
}
|
@ -4,7 +4,7 @@
|
|||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:title",
|
"autoname": "field:title",
|
||||||
"creation": "2013-01-10 16:34:24",
|
"creation": "2013-01-10 16:34:24",
|
||||||
"description": "Standard Terms and Conditions that can be added to Sales and Purchases.\n\nExamples:\n\n1. Validity of the offer.\n1. Payment Terms (In Advance, On Credit, part advance etc).\n1. What is extra (or payable by the Customer).\n1. Safety / usage warning.\n1. Warranty if any.\n1. Returns Policy.\n1. Terms of shipping, if applicable.\n1. Ways of addressing disputes, indemnity, liability, etc.\n1. Address and Contact of your Company.",
|
"description": "Standard Terms and Conditions that can be added to Sales and Purchases. Examples: Validity of the offer, Payment Terms, Safety and Usage, etc.",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
@ -77,7 +77,7 @@
|
|||||||
"icon": "icon-legal",
|
"icon": "icon-legal",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-02-01 14:33:39.246532",
|
"modified": "2024-01-30 12:47:52.325531",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Terms and Conditions",
|
"name": "Terms and Conditions",
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"autoname": "hash",
|
"autoname": "hash",
|
||||||
"creation": "2013-05-02 16:29:48",
|
"creation": "2013-05-02 16:29:48",
|
||||||
"description": "Multiple Item prices.",
|
"description": "Log the selling and buying rate of an Item",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
@ -220,7 +220,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-01-24 02:20:26.145996",
|
"modified": "2024-01-30 14:02:19.304854",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Item Price",
|
"name": "Item Price",
|
||||||
|
@ -429,6 +429,9 @@ frappe.ui.form.on("Material Request Item", {
|
|||||||
|
|
||||||
rate: function(frm, doctype, name) {
|
rate: function(frm, doctype, name) {
|
||||||
const item = locals[doctype][name];
|
const item = locals[doctype][name];
|
||||||
|
item.amount = flt(item.qty) * flt(item.rate);
|
||||||
|
frappe.model.set_value(doctype, name, "amount", item.amount);
|
||||||
|
refresh_field("amount", item.name, item.parentfield);
|
||||||
frm.events.get_item_data(frm, item, false);
|
frm.events.get_item_data(frm, item, false);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -462,6 +462,7 @@ def make_purchase_order(source_name, target_doc=None, args=None):
|
|||||||
postprocess,
|
postprocess,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
doclist.set_onload("load_after_mapping", False)
|
||||||
return doclist
|
return doclist
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,434 +1,134 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_events_in_timeline": 0,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:price_list_name",
|
"autoname": "field:price_list_name",
|
||||||
"beta": 0,
|
|
||||||
"creation": "2013-01-25 11:35:09",
|
"creation": "2013-01-25 11:35:09",
|
||||||
"custom": 0,
|
"description": "A Price List is a collection of Item Prices either Selling, Buying, or both",
|
||||||
"description": "Price List Master",
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"editable_grid": 0,
|
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"enabled",
|
||||||
|
"sb_1",
|
||||||
|
"price_list_name",
|
||||||
|
"currency",
|
||||||
|
"buying",
|
||||||
|
"selling",
|
||||||
|
"price_not_uom_dependent",
|
||||||
|
"column_break_3",
|
||||||
|
"countries"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "1",
|
"default": "1",
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "enabled",
|
"fieldname": "enabled",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"label": "Enabled"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Enabled",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "sb_1",
|
"fieldname": "sb_1",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break"
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "price_list_name",
|
"fieldname": "price_list_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Price List Name",
|
"label": "Price List Name",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "price_list_name",
|
"oldfieldname": "price_list_name",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 1
|
"unique": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "currency",
|
"fieldname": "currency",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Currency",
|
"label": "Currency",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Currency",
|
"options": "Currency",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "buying",
|
"fieldname": "buying",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
"label": "Buying"
|
||||||
"label": "Buying",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "selling",
|
"fieldname": "selling",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
"label": "Selling"
|
||||||
"label": "Selling",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "price_not_uom_dependent",
|
"fieldname": "price_not_uom_dependent",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"label": "Price Not UOM Dependent"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Price Not UOM Dependent",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "column_break_3",
|
"fieldname": "column_break_3",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break"
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "countries",
|
"fieldname": "countries",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Applicable for Countries",
|
"label": "Applicable for Countries",
|
||||||
"length": 0,
|
"options": "Price List Country"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Price List Country",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"icon": "fa fa-tags",
|
"icon": "fa fa-tags",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"image_view": 0,
|
"links": [],
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 1,
|
"max_attachments": 1,
|
||||||
"modified": "2019-06-24 17:16:28.027302",
|
"modified": "2024-01-30 14:39:26.328837",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Price List",
|
"name": "Price List",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 0,
|
|
||||||
"delete": 0,
|
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Sales User",
|
"role": "Sales User"
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 0,
|
|
||||||
"export": 1,
|
"export": 1,
|
||||||
"if_owner": 0,
|
|
||||||
"import": 1,
|
"import": 1,
|
||||||
"permlevel": 0,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Sales Master Manager",
|
"role": "Sales Master Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 0,
|
|
||||||
"delete": 0,
|
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Purchase User",
|
"role": "Purchase User"
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Purchase Master Manager",
|
"role": "Purchase Master Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 0,
|
|
||||||
"delete": 0,
|
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 0,
|
"role": "Manufacturing User"
|
||||||
"role": "Manufacturing User",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"search_fields": "currency",
|
"search_fields": "currency",
|
||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
"sort_order": "ASC",
|
"sort_order": "ASC",
|
||||||
"track_changes": 0,
|
"states": []
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
@ -8,8 +8,10 @@ frappe.listview_settings['Purchase Receipt'] = {
|
|||||||
return [__("Closed"), "green", "status,=,Closed"];
|
return [__("Closed"), "green", "status,=,Closed"];
|
||||||
} else if (flt(doc.per_returned, 2) === 100) {
|
} else if (flt(doc.per_returned, 2) === 100) {
|
||||||
return [__("Return Issued"), "grey", "per_returned,=,100"];
|
return [__("Return Issued"), "grey", "per_returned,=,100"];
|
||||||
} else if (flt(doc.grand_total) !== 0 && flt(doc.per_billed, 2) < 100) {
|
} else if (flt(doc.grand_total) !== 0 && flt(doc.per_billed, 2) == 0) {
|
||||||
return [__("To Bill"), "orange", "per_billed,<,100"];
|
return [__("To Bill"), "orange", "per_billed,<,100"];
|
||||||
|
} else if (flt(doc.per_billed, 2) > 0 && flt(doc.per_billed, 2) < 100) {
|
||||||
|
return [__("Partly Billed"), "yellow", "per_billed,<,100"];
|
||||||
} else if (flt(doc.grand_total) === 0 || flt(doc.per_billed, 2) === 100) {
|
} else if (flt(doc.grand_total) === 0 || flt(doc.per_billed, 2) === 100) {
|
||||||
return [__("Completed"), "green", "per_billed,=,100"];
|
return [__("Completed"), "green", "per_billed,=,100"];
|
||||||
}
|
}
|
||||||
|
@ -720,7 +720,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
pr2.load_from_db()
|
pr2.load_from_db()
|
||||||
self.assertEqual(pr2.get("items")[0].billed_amt, 2000)
|
self.assertEqual(pr2.get("items")[0].billed_amt, 2000)
|
||||||
self.assertEqual(pr2.per_billed, 80)
|
self.assertEqual(pr2.per_billed, 80)
|
||||||
self.assertEqual(pr2.status, "To Bill")
|
self.assertEqual(pr2.status, "Partly Billed")
|
||||||
|
|
||||||
pr2.cancel()
|
pr2.cancel()
|
||||||
pi2.reload()
|
pi2.reload()
|
||||||
@ -1152,7 +1152,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
pi.load_from_db()
|
pi.load_from_db()
|
||||||
pr.load_from_db()
|
pr.load_from_db()
|
||||||
|
|
||||||
self.assertEqual(pr.status, "To Bill")
|
self.assertEqual(pr.status, "Partly Billed")
|
||||||
self.assertAlmostEqual(pr.per_billed, 50.0, places=2)
|
self.assertAlmostEqual(pr.per_billed, 50.0, places=2)
|
||||||
|
|
||||||
def test_purchase_receipt_with_exchange_rate_difference(self):
|
def test_purchase_receipt_with_exchange_rate_difference(self):
|
||||||
@ -1675,9 +1675,10 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
make_stock_entry(
|
make_stock_entry(
|
||||||
purpose="Material Receipt",
|
purpose="Material Receipt",
|
||||||
item_code=item.name,
|
item_code=item.name,
|
||||||
qty=15,
|
qty=20,
|
||||||
company=company,
|
company=company,
|
||||||
to_warehouse=from_warehouse,
|
to_warehouse=from_warehouse,
|
||||||
|
posting_date=add_days(today(), -3),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Step 3: Create Delivery Note with Internal Customer
|
# Step 3: Create Delivery Note with Internal Customer
|
||||||
@ -1700,6 +1701,8 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
|
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
|
||||||
|
|
||||||
pr = make_inter_company_purchase_receipt(dn.name)
|
pr = make_inter_company_purchase_receipt(dn.name)
|
||||||
|
pr.set_posting_time = 1
|
||||||
|
pr.posting_date = today()
|
||||||
pr.items[0].qty = 15
|
pr.items[0].qty = 15
|
||||||
pr.items[0].from_warehouse = target_warehouse
|
pr.items[0].from_warehouse = target_warehouse
|
||||||
pr.items[0].warehouse = to_warehouse
|
pr.items[0].warehouse = to_warehouse
|
||||||
@ -1718,6 +1721,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
company=company,
|
company=company,
|
||||||
from_warehouse=from_warehouse,
|
from_warehouse=from_warehouse,
|
||||||
to_warehouse=target_warehouse,
|
to_warehouse=target_warehouse,
|
||||||
|
posting_date=add_days(pr.posting_date, -1),
|
||||||
)
|
)
|
||||||
|
|
||||||
pr.reload()
|
pr.reload()
|
||||||
|
@ -228,7 +228,6 @@ class StockEntry(StockController):
|
|||||||
self.fg_completed_qty = 0.0
|
self.fg_completed_qty = 0.0
|
||||||
|
|
||||||
self.validate_serialized_batch()
|
self.validate_serialized_batch()
|
||||||
self.set_actual_qty()
|
|
||||||
self.calculate_rate_and_amount()
|
self.calculate_rate_and_amount()
|
||||||
self.validate_putaway_capacity()
|
self.validate_putaway_capacity()
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
"creation": "2013-06-24 16:37:54",
|
"creation": "2013-06-24 16:37:54",
|
||||||
"description": "Settings",
|
"description": "Default settings for your stock-related transactions",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
@ -427,7 +427,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-01-24 02:20:26.145996",
|
"modified": "2024-01-30 14:03:52.143457",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Settings",
|
"name": "Stock Settings",
|
||||||
|
@ -543,7 +543,7 @@ def get_item_tax_info(company, tax_category, item_codes, item_rates=None, item_t
|
|||||||
args = {
|
args = {
|
||||||
"company": company,
|
"company": company,
|
||||||
"tax_category": tax_category,
|
"tax_category": tax_category,
|
||||||
"net_rate": item_rates.get(item_code[1]),
|
"base_net_rate": item_rates.get(item_code[1]),
|
||||||
}
|
}
|
||||||
|
|
||||||
if item_tax_templates:
|
if item_tax_templates:
|
||||||
@ -635,7 +635,7 @@ def is_within_valid_range(args, tax):
|
|||||||
if not flt(tax.maximum_net_rate):
|
if not flt(tax.maximum_net_rate):
|
||||||
# No range specified, just ignore
|
# No range specified, just ignore
|
||||||
return True
|
return True
|
||||||
elif flt(tax.minimum_net_rate) <= flt(args.get("net_rate")) <= flt(tax.maximum_net_rate):
|
elif flt(tax.minimum_net_rate) <= flt(args.get("base_net_rate")) <= flt(tax.maximum_net_rate):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
@ -897,9 +897,12 @@ class update_entries_after(object):
|
|||||||
|
|
||||||
self.wh_data.stock_value = round_off_if_near_zero(self.wh_data.stock_value + doc.total_amount)
|
self.wh_data.stock_value = round_off_if_near_zero(self.wh_data.stock_value + doc.total_amount)
|
||||||
|
|
||||||
self.wh_data.qty_after_transaction += doc.total_qty
|
precision = doc.precision("total_qty")
|
||||||
|
self.wh_data.qty_after_transaction += flt(doc.total_qty, precision)
|
||||||
if self.wh_data.qty_after_transaction:
|
if self.wh_data.qty_after_transaction:
|
||||||
self.wh_data.valuation_rate = self.wh_data.stock_value / self.wh_data.qty_after_transaction
|
self.wh_data.valuation_rate = flt(self.wh_data.stock_value, precision) / flt(
|
||||||
|
self.wh_data.qty_after_transaction, precision
|
||||||
|
)
|
||||||
|
|
||||||
def validate_negative_stock(self, sle):
|
def validate_negative_stock(self, sle):
|
||||||
"""
|
"""
|
||||||
|
@ -34,6 +34,18 @@
|
|||||||
</a>
|
</a>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{% if show_pay_button %}
|
||||||
|
<div class="form-column col-sm-6">
|
||||||
|
<div class="page-header-actions-block" data-html-block="header-actions">
|
||||||
|
<p>
|
||||||
|
<a href="/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn={{ doc.name }}&dt={{ doc.doctype }}&submit_doc=1&order_type=Shopping Cart"
|
||||||
|
class="btn btn-primary btn-sm" id="pay-for-order">
|
||||||
|
{{ _("Pay") }} {{doc.get_formatted("grand_total") }}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -48,7 +48,10 @@ def get_context(context):
|
|||||||
)
|
)
|
||||||
context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points"))
|
context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points"))
|
||||||
|
|
||||||
context.show_pay_button = frappe.db.get_single_value("Buying Settings", "show_pay_button")
|
context.show_pay_button = (
|
||||||
|
"payments" in frappe.get_installed_apps()
|
||||||
|
and frappe.db.get_single_value("Buying Settings", "show_pay_button")
|
||||||
|
)
|
||||||
context.show_make_pi_button = False
|
context.show_make_pi_button = False
|
||||||
if context.doc.get("supplier"):
|
if context.doc.get("supplier"):
|
||||||
# show Make Purchase Invoice button based on permission
|
# show Make Purchase Invoice button based on permission
|
||||||
|
Loading…
x
Reference in New Issue
Block a user