Merge branch 'develop' into multiple-pricing-rule-fix
This commit is contained in:
commit
1abdcf2bea
10
codecov.yml
10
codecov.yml
@ -8,6 +8,16 @@ coverage:
|
|||||||
target: auto
|
target: auto
|
||||||
threshold: 0.5%
|
threshold: 0.5%
|
||||||
|
|
||||||
|
patch:
|
||||||
|
default:
|
||||||
|
target: 85%
|
||||||
|
threshold: 0%
|
||||||
|
base: auto
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
if_ci_failed: ignore
|
||||||
|
only_pulls: true
|
||||||
|
|
||||||
comment:
|
comment:
|
||||||
layout: "diff, files"
|
layout: "diff, files"
|
||||||
require_changes: true
|
require_changes: true
|
||||||
|
@ -159,7 +159,8 @@ class OpeningInvoiceCreationTool(Document):
|
|||||||
frappe.scrub(row.party_type): row.party,
|
frappe.scrub(row.party_type): row.party,
|
||||||
"is_pos": 0,
|
"is_pos": 0,
|
||||||
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
|
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
|
||||||
"update_stock": 0
|
"update_stock": 0,
|
||||||
|
"invoice_number": row.invoice_number
|
||||||
})
|
})
|
||||||
|
|
||||||
accounting_dimension = get_accounting_dimensions()
|
accounting_dimension = get_accounting_dimensions()
|
||||||
@ -200,10 +201,13 @@ def start_import(invoices):
|
|||||||
names = []
|
names = []
|
||||||
for idx, d in enumerate(invoices):
|
for idx, d in enumerate(invoices):
|
||||||
try:
|
try:
|
||||||
|
invoice_number = None
|
||||||
|
if d.invoice_number:
|
||||||
|
invoice_number = d.invoice_number
|
||||||
publish(idx, len(invoices), d.doctype)
|
publish(idx, len(invoices), d.doctype)
|
||||||
doc = frappe.get_doc(d)
|
doc = frappe.get_doc(d)
|
||||||
doc.flags.ignore_mandatory = True
|
doc.flags.ignore_mandatory = True
|
||||||
doc.insert()
|
doc.insert(set_name=invoice_number)
|
||||||
doc.submit()
|
doc.submit()
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
names.append(doc.name)
|
names.append(doc.name)
|
||||||
|
@ -18,10 +18,10 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
|||||||
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
|
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
|
||||||
make_company()
|
make_company()
|
||||||
|
|
||||||
def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None):
|
def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None):
|
||||||
doc = frappe.get_single("Opening Invoice Creation Tool")
|
doc = frappe.get_single("Opening Invoice Creation Tool")
|
||||||
args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
|
args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
|
||||||
party_1=party_1, party_2=party_2)
|
party_1=party_1, party_2=party_2, invoice_number=invoice_number)
|
||||||
doc.update(args)
|
doc.update(args)
|
||||||
return doc.make_invoices()
|
return doc.make_invoices()
|
||||||
|
|
||||||
@ -92,6 +92,20 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
|||||||
# teardown
|
# teardown
|
||||||
frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account)
|
frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account)
|
||||||
|
|
||||||
|
def test_renaming_of_invoice_using_invoice_number_field(self):
|
||||||
|
company = "_Test Opening Invoice Company"
|
||||||
|
party_1, party_2 = make_customer("Customer A"), make_customer("Customer B")
|
||||||
|
self.make_invoices(company=company, party_1=party_1, party_2=party_2, invoice_number="TEST-NEW-INV-11")
|
||||||
|
|
||||||
|
sales_inv1 = frappe.get_all('Sales Invoice', filters={'customer':'Customer A'})[0].get("name")
|
||||||
|
sales_inv2 = frappe.get_all('Sales Invoice', filters={'customer':'Customer B'})[0].get("name")
|
||||||
|
self.assertEqual(sales_inv1, "TEST-NEW-INV-11")
|
||||||
|
|
||||||
|
#teardown
|
||||||
|
for inv in [sales_inv1, sales_inv2]:
|
||||||
|
doc = frappe.get_doc('Sales Invoice', inv)
|
||||||
|
doc.cancel()
|
||||||
|
|
||||||
def get_opening_invoice_creation_dict(**args):
|
def get_opening_invoice_creation_dict(**args):
|
||||||
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
|
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
|
||||||
company = args.get("company", "_Test Company")
|
company = args.get("company", "_Test Company")
|
||||||
@ -107,7 +121,8 @@ def get_opening_invoice_creation_dict(**args):
|
|||||||
"item_name": "Opening Item",
|
"item_name": "Opening Item",
|
||||||
"due_date": "2016-09-10",
|
"due_date": "2016-09-10",
|
||||||
"posting_date": "2016-09-05",
|
"posting_date": "2016-09-05",
|
||||||
"temporary_opening_account": get_temporary_opening_account(company)
|
"temporary_opening_account": get_temporary_opening_account(company),
|
||||||
|
"invoice_number": args.get("invoice_number")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"qty": 2.0,
|
"qty": 2.0,
|
||||||
@ -116,7 +131,8 @@ def get_opening_invoice_creation_dict(**args):
|
|||||||
"item_name": "Opening Item",
|
"item_name": "Opening Item",
|
||||||
"due_date": "2016-09-10",
|
"due_date": "2016-09-10",
|
||||||
"posting_date": "2016-09-05",
|
"posting_date": "2016-09-05",
|
||||||
"temporary_opening_account": get_temporary_opening_account(company)
|
"temporary_opening_account": get_temporary_opening_account(company),
|
||||||
|
"invoice_number": None
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"creation": "2017-08-29 04:26:36.159247",
|
"creation": "2017-08-29 04:26:36.159247",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
|
"invoice_number",
|
||||||
"party_type",
|
"party_type",
|
||||||
"party",
|
"party",
|
||||||
"temporary_opening_account",
|
"temporary_opening_account",
|
||||||
@ -103,10 +105,18 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "dimension_col_break",
|
"fieldname": "dimension_col_break",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Reference number of the invoice from the previous system",
|
||||||
|
"fieldname": "invoice_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Invoice Number"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2019-07-25 15:00:00.460695",
|
"links": [],
|
||||||
|
"modified": "2021-12-17 19:25:06.053187",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Opening Invoice Creation Tool Item",
|
"name": "Opening Invoice Creation Tool Item",
|
||||||
|
@ -23,6 +23,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
|||||||
get_accounting_dimensions,
|
get_accounting_dimensions,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
|
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
|
||||||
|
from erpnext.accounts.party import get_party_account_currency
|
||||||
|
|
||||||
|
|
||||||
class Subscription(Document):
|
class Subscription(Document):
|
||||||
@ -355,6 +356,9 @@ class Subscription(Document):
|
|||||||
if frappe.db.get_value('Supplier', self.party, 'tax_withholding_category'):
|
if frappe.db.get_value('Supplier', self.party, 'tax_withholding_category'):
|
||||||
invoice.apply_tds = 1
|
invoice.apply_tds = 1
|
||||||
|
|
||||||
|
### Add party currency to invoice
|
||||||
|
invoice.currency = get_party_account_currency(self.party_type, self.party, self.company)
|
||||||
|
|
||||||
## Add dimensions in invoice for subscription:
|
## Add dimensions in invoice for subscription:
|
||||||
accounting_dimensions = get_accounting_dimensions()
|
accounting_dimensions = get_accounting_dimensions()
|
||||||
|
|
||||||
|
@ -60,15 +60,38 @@ def create_plan():
|
|||||||
plan.billing_interval_count = 3
|
plan.billing_interval_count = 3
|
||||||
plan.insert()
|
plan.insert()
|
||||||
|
|
||||||
|
if not frappe.db.exists('Subscription Plan', '_Test Plan Multicurrency'):
|
||||||
|
plan = frappe.new_doc('Subscription Plan')
|
||||||
|
plan.plan_name = '_Test Plan Multicurrency'
|
||||||
|
plan.item = '_Test Non Stock Item'
|
||||||
|
plan.price_determination = "Fixed Rate"
|
||||||
|
plan.cost = 50
|
||||||
|
plan.currency = 'USD'
|
||||||
|
plan.billing_interval = 'Month'
|
||||||
|
plan.billing_interval_count = 1
|
||||||
|
plan.insert()
|
||||||
|
|
||||||
|
def create_parties():
|
||||||
if not frappe.db.exists('Supplier', '_Test Supplier'):
|
if not frappe.db.exists('Supplier', '_Test Supplier'):
|
||||||
supplier = frappe.new_doc('Supplier')
|
supplier = frappe.new_doc('Supplier')
|
||||||
supplier.supplier_name = '_Test Supplier'
|
supplier.supplier_name = '_Test Supplier'
|
||||||
supplier.supplier_group = 'All Supplier Groups'
|
supplier.supplier_group = 'All Supplier Groups'
|
||||||
supplier.insert()
|
supplier.insert()
|
||||||
|
|
||||||
|
if not frappe.db.exists('Customer', '_Test Subscription Customer'):
|
||||||
|
customer = frappe.new_doc('Customer')
|
||||||
|
customer.customer_name = '_Test Subscription Customer'
|
||||||
|
customer.billing_currency = 'USD'
|
||||||
|
customer.append('accounts', {
|
||||||
|
'company': '_Test Company',
|
||||||
|
'account': '_Test Receivable USD - _TC'
|
||||||
|
})
|
||||||
|
customer.insert()
|
||||||
|
|
||||||
class TestSubscription(unittest.TestCase):
|
class TestSubscription(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
create_plan()
|
create_plan()
|
||||||
|
create_parties()
|
||||||
|
|
||||||
def test_create_subscription_with_trial_with_correct_period(self):
|
def test_create_subscription_with_trial_with_correct_period(self):
|
||||||
subscription = frappe.new_doc('Subscription')
|
subscription = frappe.new_doc('Subscription')
|
||||||
@ -637,3 +660,22 @@ class TestSubscription(unittest.TestCase):
|
|||||||
|
|
||||||
subscription.process()
|
subscription.process()
|
||||||
self.assertEqual(len(subscription.invoices), 1)
|
self.assertEqual(len(subscription.invoices), 1)
|
||||||
|
|
||||||
|
def test_multicurrency_subscription(self):
|
||||||
|
subscription = frappe.new_doc('Subscription')
|
||||||
|
subscription.party_type = 'Customer'
|
||||||
|
subscription.party = '_Test Subscription Customer'
|
||||||
|
subscription.generate_invoice_at_period_start = 1
|
||||||
|
subscription.company = '_Test Company'
|
||||||
|
# select subscription start date as '2018-01-15'
|
||||||
|
subscription.start_date = '2018-01-01'
|
||||||
|
subscription.append('plans', {'plan': '_Test Plan Multicurrency', 'qty': 1})
|
||||||
|
subscription.save()
|
||||||
|
|
||||||
|
subscription.process()
|
||||||
|
self.assertEqual(len(subscription.invoices), 1)
|
||||||
|
self.assertEqual(subscription.status, 'Unpaid')
|
||||||
|
|
||||||
|
# Check the currency of the created invoice
|
||||||
|
currency = frappe.db.get_value('Sales Invoice', subscription.invoices[0].invoice, 'currency')
|
||||||
|
self.assertEqual(currency, 'USD')
|
@ -75,7 +75,8 @@
|
|||||||
"fieldname": "cost",
|
"fieldname": "cost",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Cost"
|
"label": "Cost",
|
||||||
|
"options": "currency"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.price_determination==\"Based On Price List\"",
|
"depends_on": "eval:doc.price_determination==\"Based On Price List\"",
|
||||||
@ -147,7 +148,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-08-13 10:53:44.205774",
|
"modified": "2021-12-10 15:24:15.794477",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Subscription Plan",
|
"name": "Subscription Plan",
|
||||||
|
@ -0,0 +1,114 @@
|
|||||||
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
function get_filters() {
|
||||||
|
let filters = [
|
||||||
|
{
|
||||||
|
"fieldname":"company",
|
||||||
|
"label": __("Company"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Company",
|
||||||
|
"default": frappe.defaults.get_user_default("Company"),
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"filter_based_on",
|
||||||
|
"label": __("Filter Based On"),
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": ["Fiscal Year", "Date Range"],
|
||||||
|
"default": ["Fiscal Year"],
|
||||||
|
"reqd": 1,
|
||||||
|
on_change: function() {
|
||||||
|
let filter_based_on = frappe.query_report.get_filter_value('filter_based_on');
|
||||||
|
frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range');
|
||||||
|
frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range');
|
||||||
|
frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year');
|
||||||
|
frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year');
|
||||||
|
|
||||||
|
frappe.query_report.refresh();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"period_start_date",
|
||||||
|
"label": __("Start Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"hidden": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"period_end_date",
|
||||||
|
"label": __("End Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"hidden": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"from_fiscal_year",
|
||||||
|
"label": __("Start Year"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Fiscal Year",
|
||||||
|
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"to_fiscal_year",
|
||||||
|
"label": __("End Year"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Fiscal Year",
|
||||||
|
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "periodicity",
|
||||||
|
"label": __("Periodicity"),
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": [
|
||||||
|
{ "value": "Monthly", "label": __("Monthly") },
|
||||||
|
{ "value": "Quarterly", "label": __("Quarterly") },
|
||||||
|
{ "value": "Half-Yearly", "label": __("Half-Yearly") },
|
||||||
|
{ "value": "Yearly", "label": __("Yearly") }
|
||||||
|
],
|
||||||
|
"default": "Monthly",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "type",
|
||||||
|
"label": __("Invoice Type"),
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": [
|
||||||
|
{ "value": "Revenue", "label": __("Revenue") },
|
||||||
|
{ "value": "Expense", "label": __("Expense") }
|
||||||
|
],
|
||||||
|
"default": "Revenue",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname" : "with_upcoming_postings",
|
||||||
|
"label": __("Show with upcoming revenue/expense"),
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.query_reports["Deferred Revenue and Expense"] = {
|
||||||
|
"filters": get_filters(),
|
||||||
|
"formatter": function(value, row, column, data, default_formatter){
|
||||||
|
return default_formatter(value, row, column, data);
|
||||||
|
},
|
||||||
|
onload: function(report){
|
||||||
|
let fiscal_year = frappe.defaults.get_user_default("fiscal_year");
|
||||||
|
|
||||||
|
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||||
|
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||||
|
frappe.query_report.set_filter_value({
|
||||||
|
period_start_date: fy.year_start_date,
|
||||||
|
period_end_date: fy.year_end_date
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 0,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2021-12-10 19:27:14.654220",
|
||||||
|
"disable_prepared_report": 0,
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"modified": "2021-12-10 19:27:14.654220",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Deferred Revenue and Expense",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "GL Entry",
|
||||||
|
"report_name": "Deferred Revenue and Expense",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "Accounts User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Accounts Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Auditor"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,440 @@
|
|||||||
|
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# License: MIT. See LICENSE
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _, qb
|
||||||
|
from frappe.query_builder import Column, functions
|
||||||
|
from frappe.utils import add_days, date_diff, flt, get_first_day, get_last_day, rounded
|
||||||
|
|
||||||
|
from erpnext.accounts.report.financial_statements import get_period_list
|
||||||
|
|
||||||
|
|
||||||
|
class Deferred_Item(object):
|
||||||
|
"""
|
||||||
|
Helper class for processing items with deferred revenue/expense
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, item, inv, gle_entries):
|
||||||
|
self.name = item
|
||||||
|
self.parent = inv.name
|
||||||
|
self.item_name = gle_entries[0].item_name
|
||||||
|
self.service_start_date = gle_entries[0].service_start_date
|
||||||
|
self.service_end_date = gle_entries[0].service_end_date
|
||||||
|
self.base_net_amount = gle_entries[0].base_net_amount
|
||||||
|
self.filters = inv.filters
|
||||||
|
self.period_list = inv.period_list
|
||||||
|
|
||||||
|
if gle_entries[0].deferred_revenue_account:
|
||||||
|
self.type = "Deferred Sale Item"
|
||||||
|
self.deferred_account = gle_entries[0].deferred_revenue_account
|
||||||
|
elif gle_entries[0].deferred_expense_account:
|
||||||
|
self.type = "Deferred Purchase Item"
|
||||||
|
self.deferred_account = gle_entries[0].deferred_expense_account
|
||||||
|
|
||||||
|
self.gle_entries = []
|
||||||
|
# holds period wise total for item
|
||||||
|
self.period_total = []
|
||||||
|
self.last_entry_date = self.service_start_date
|
||||||
|
|
||||||
|
if gle_entries:
|
||||||
|
self.gle_entries = gle_entries
|
||||||
|
for x in self.gle_entries:
|
||||||
|
if self.get_amount(x):
|
||||||
|
self.last_entry_date = x.gle_posting_date
|
||||||
|
|
||||||
|
def report_data(self):
|
||||||
|
"""
|
||||||
|
Generate report data for output
|
||||||
|
"""
|
||||||
|
ret_data = frappe._dict({"name": self.item_name})
|
||||||
|
for period in self.period_total:
|
||||||
|
ret_data[period.key] = period.total
|
||||||
|
ret_data.indent = 1
|
||||||
|
return ret_data
|
||||||
|
|
||||||
|
def get_amount(self, entry):
|
||||||
|
"""
|
||||||
|
For a given GL/Journal posting, get balance based on item type
|
||||||
|
"""
|
||||||
|
if self.type == "Deferred Sale Item":
|
||||||
|
return entry.debit - entry.credit
|
||||||
|
elif self.type == "Deferred Purchase Item":
|
||||||
|
return -(entry.credit - entry.debit)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def get_item_total(self):
|
||||||
|
"""
|
||||||
|
Helper method - calculate booked amount. Includes simulated postings as well
|
||||||
|
"""
|
||||||
|
total = 0
|
||||||
|
for gle_posting in self.gle_entries:
|
||||||
|
total += self.get_amount(gle_posting)
|
||||||
|
|
||||||
|
return total
|
||||||
|
|
||||||
|
def calculate_amount(self, start_date, end_date):
|
||||||
|
"""
|
||||||
|
start_date, end_date - datetime.datetime.date
|
||||||
|
return - estimated amount to post for given period
|
||||||
|
Calculated based on already booked amount and item service period
|
||||||
|
"""
|
||||||
|
total_months = (
|
||||||
|
(self.service_end_date.year - self.service_start_date.year) * 12
|
||||||
|
+ (self.service_end_date.month - self.service_start_date.month)
|
||||||
|
+ 1
|
||||||
|
)
|
||||||
|
|
||||||
|
prorate = date_diff(self.service_end_date, self.service_start_date) / date_diff(
|
||||||
|
get_last_day(self.service_end_date), get_first_day(self.service_start_date)
|
||||||
|
)
|
||||||
|
|
||||||
|
actual_months = rounded(total_months * prorate, 1)
|
||||||
|
|
||||||
|
already_booked_amount = self.get_item_total()
|
||||||
|
base_amount = self.base_net_amount / actual_months
|
||||||
|
|
||||||
|
if base_amount + already_booked_amount > self.base_net_amount:
|
||||||
|
base_amount = self.base_net_amount - already_booked_amount
|
||||||
|
|
||||||
|
if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date):
|
||||||
|
partial_month = flt(date_diff(end_date, start_date)) / flt(
|
||||||
|
date_diff(get_last_day(end_date), get_first_day(start_date))
|
||||||
|
)
|
||||||
|
base_amount *= rounded(partial_month, 1)
|
||||||
|
|
||||||
|
return base_amount
|
||||||
|
|
||||||
|
def make_dummy_gle(self, name, date, amount):
|
||||||
|
"""
|
||||||
|
return - frappe._dict() of a dummy gle entry
|
||||||
|
"""
|
||||||
|
entry = frappe._dict(
|
||||||
|
{"name": name, "gle_posting_date": date, "debit": 0, "credit": 0, "posted": "not"}
|
||||||
|
)
|
||||||
|
if self.type == "Deferred Sale Item":
|
||||||
|
entry.debit = amount
|
||||||
|
elif self.type == "Deferred Purchase Item":
|
||||||
|
entry.credit = amount
|
||||||
|
return entry
|
||||||
|
|
||||||
|
def simulate_future_posting(self):
|
||||||
|
"""
|
||||||
|
simulate future posting by creating dummy gl entries. starts from the last posting date.
|
||||||
|
"""
|
||||||
|
if add_days(self.last_entry_date, 1) < self.period_list[-1].to_date:
|
||||||
|
self.estimate_for_period_list = get_period_list(
|
||||||
|
self.filters.from_fiscal_year,
|
||||||
|
self.filters.to_fiscal_year,
|
||||||
|
add_days(self.last_entry_date, 1),
|
||||||
|
self.period_list[-1].to_date,
|
||||||
|
"Date Range",
|
||||||
|
"Monthly",
|
||||||
|
company=self.filters.company,
|
||||||
|
)
|
||||||
|
for period in self.estimate_for_period_list:
|
||||||
|
amount = self.calculate_amount(period.from_date, period.to_date)
|
||||||
|
gle = self.make_dummy_gle(period.key, period.to_date, amount)
|
||||||
|
self.gle_entries.append(gle)
|
||||||
|
|
||||||
|
def calculate_item_revenue_expense_for_period(self):
|
||||||
|
"""
|
||||||
|
calculate item postings for each period and update period_total list
|
||||||
|
"""
|
||||||
|
for period in self.period_list:
|
||||||
|
period_sum = 0
|
||||||
|
actual = 0
|
||||||
|
for posting in self.gle_entries:
|
||||||
|
# if period.from_date <= posting.posting_date <= period.to_date:
|
||||||
|
if period.from_date <= posting.gle_posting_date <= period.to_date:
|
||||||
|
period_sum += self.get_amount(posting)
|
||||||
|
if posting.posted == "posted":
|
||||||
|
actual += self.get_amount(posting)
|
||||||
|
|
||||||
|
self.period_total.append(
|
||||||
|
frappe._dict({"key": period.key, "total": period_sum, "actual": actual})
|
||||||
|
)
|
||||||
|
return self.period_total
|
||||||
|
|
||||||
|
|
||||||
|
class Deferred_Invoice(object):
|
||||||
|
def __init__(self, invoice, items, filters, period_list):
|
||||||
|
"""
|
||||||
|
Helper class for processing invoices with deferred revenue/expense items
|
||||||
|
invoice - string : invoice name
|
||||||
|
items - list : frappe._dict() with item details. Refer Deferred_Item for required fields
|
||||||
|
"""
|
||||||
|
self.name = invoice
|
||||||
|
self.posting_date = items[0].posting_date
|
||||||
|
self.filters = filters
|
||||||
|
self.period_list = period_list
|
||||||
|
# holds period wise total for invoice
|
||||||
|
self.period_total = []
|
||||||
|
|
||||||
|
if items[0].deferred_revenue_account:
|
||||||
|
self.type = "Sales"
|
||||||
|
elif items[0].deferred_expense_account:
|
||||||
|
self.type = "Purchase"
|
||||||
|
|
||||||
|
self.items = []
|
||||||
|
# for each uniq items
|
||||||
|
self.uniq_items = set([x.item for x in items])
|
||||||
|
for item in self.uniq_items:
|
||||||
|
self.items.append(Deferred_Item(item, self, [x for x in items if x.item == item]))
|
||||||
|
|
||||||
|
def calculate_invoice_revenue_expense_for_period(self):
|
||||||
|
"""
|
||||||
|
calculate deferred revenue/expense for all items in invoice
|
||||||
|
"""
|
||||||
|
# initialize period_total list for invoice
|
||||||
|
for period in self.period_list:
|
||||||
|
self.period_total.append(frappe._dict({"key": period.key, "total": 0, "actual": 0}))
|
||||||
|
|
||||||
|
for item in self.items:
|
||||||
|
item_total = item.calculate_item_revenue_expense_for_period()
|
||||||
|
# update invoice total
|
||||||
|
for idx, period in enumerate(self.period_list, 0):
|
||||||
|
self.period_total[idx].total += item_total[idx].total
|
||||||
|
self.period_total[idx].actual += item_total[idx].actual
|
||||||
|
return self.period_total
|
||||||
|
|
||||||
|
def estimate_future(self):
|
||||||
|
"""
|
||||||
|
create dummy GL entries for upcoming months for all items in invoice
|
||||||
|
"""
|
||||||
|
[item.simulate_future_posting() for item in self.items]
|
||||||
|
|
||||||
|
def report_data(self):
|
||||||
|
"""
|
||||||
|
generate report data for invoice, includes invoice total
|
||||||
|
"""
|
||||||
|
ret_data = []
|
||||||
|
inv_total = frappe._dict({"name": self.name})
|
||||||
|
for x in self.period_total:
|
||||||
|
inv_total[x.key] = x.total
|
||||||
|
inv_total.indent = 0
|
||||||
|
ret_data.append(inv_total)
|
||||||
|
list(map(lambda item: ret_data.append(item.report_data()), self.items))
|
||||||
|
return ret_data
|
||||||
|
|
||||||
|
|
||||||
|
class Deferred_Revenue_and_Expense_Report(object):
|
||||||
|
def __init__(self, filters=None):
|
||||||
|
"""
|
||||||
|
Initialize deferred revenue/expense report with user provided filters or system defaults, if none is provided
|
||||||
|
"""
|
||||||
|
|
||||||
|
# If no filters are provided, get user defaults
|
||||||
|
if not filters:
|
||||||
|
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
||||||
|
self.filters = frappe._dict(
|
||||||
|
{
|
||||||
|
"company": frappe.defaults.get_user_default("Company"),
|
||||||
|
"filter_based_on": "Fiscal Year",
|
||||||
|
"period_start_date": fiscal_year.year_start_date,
|
||||||
|
"period_end_date": fiscal_year.year_end_date,
|
||||||
|
"from_fiscal_year": fiscal_year.year,
|
||||||
|
"to_fiscal_year": fiscal_year.year,
|
||||||
|
"periodicity": "Monthly",
|
||||||
|
"type": "Revenue",
|
||||||
|
"with_upcoming_postings": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.filters = frappe._dict(filters)
|
||||||
|
|
||||||
|
self.period_list = None
|
||||||
|
self.deferred_invoices = []
|
||||||
|
# holds period wise total for report
|
||||||
|
self.period_total = []
|
||||||
|
|
||||||
|
def get_period_list(self):
|
||||||
|
"""
|
||||||
|
Figure out selected period based on filters
|
||||||
|
"""
|
||||||
|
self.period_list = get_period_list(
|
||||||
|
self.filters.from_fiscal_year,
|
||||||
|
self.filters.to_fiscal_year,
|
||||||
|
self.filters.period_start_date,
|
||||||
|
self.filters.period_end_date,
|
||||||
|
self.filters.filter_based_on,
|
||||||
|
self.filters.periodicity,
|
||||||
|
company=self.filters.company,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_invoices(self):
|
||||||
|
"""
|
||||||
|
Get all sales and purchase invoices which has deferred revenue/expense items
|
||||||
|
"""
|
||||||
|
gle = qb.DocType("GL Entry")
|
||||||
|
# column doesn't have an alias option
|
||||||
|
posted = Column("posted")
|
||||||
|
|
||||||
|
if self.filters.type == "Revenue":
|
||||||
|
inv = qb.DocType("Sales Invoice")
|
||||||
|
inv_item = qb.DocType("Sales Invoice Item")
|
||||||
|
deferred_flag_field = inv_item["enable_deferred_revenue"]
|
||||||
|
deferred_account_field = inv_item["deferred_revenue_account"]
|
||||||
|
|
||||||
|
elif self.filters.type == "Expense":
|
||||||
|
inv = qb.DocType("Purchase Invoice")
|
||||||
|
inv_item = qb.DocType("Purchase Invoice Item")
|
||||||
|
deferred_flag_field = inv_item["enable_deferred_expense"]
|
||||||
|
deferred_account_field = inv_item["deferred_expense_account"]
|
||||||
|
|
||||||
|
query = (
|
||||||
|
qb.from_(inv_item)
|
||||||
|
.join(inv)
|
||||||
|
.on(inv.name == inv_item.parent)
|
||||||
|
.join(gle)
|
||||||
|
.on((inv_item.name == gle.voucher_detail_no) & (deferred_account_field == gle.account))
|
||||||
|
.select(
|
||||||
|
inv.name.as_("doc"),
|
||||||
|
inv.posting_date,
|
||||||
|
inv_item.name.as_("item"),
|
||||||
|
inv_item.item_name,
|
||||||
|
inv_item.service_start_date,
|
||||||
|
inv_item.service_end_date,
|
||||||
|
inv_item.base_net_amount,
|
||||||
|
deferred_account_field,
|
||||||
|
gle.posting_date.as_("gle_posting_date"),
|
||||||
|
functions.Sum(gle.debit).as_("debit"),
|
||||||
|
functions.Sum(gle.credit).as_("credit"),
|
||||||
|
posted,
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(inv.docstatus == 1)
|
||||||
|
& (deferred_flag_field == 1)
|
||||||
|
& (
|
||||||
|
(
|
||||||
|
(self.period_list[0].from_date >= inv_item.service_start_date)
|
||||||
|
& (inv_item.service_end_date >= self.period_list[0].from_date)
|
||||||
|
)
|
||||||
|
| (
|
||||||
|
(inv_item.service_start_date >= self.period_list[0].from_date)
|
||||||
|
& (inv_item.service_start_date <= self.period_list[-1].to_date)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.groupby(inv.name, inv_item.name, gle.posting_date)
|
||||||
|
.orderby(gle.posting_date)
|
||||||
|
)
|
||||||
|
self.invoices = query.run(as_dict=True)
|
||||||
|
|
||||||
|
uniq_invoice = set([x.doc for x in self.invoices])
|
||||||
|
for inv in uniq_invoice:
|
||||||
|
self.deferred_invoices.append(
|
||||||
|
Deferred_Invoice(
|
||||||
|
inv, [x for x in self.invoices if x.doc == inv], self.filters, self.period_list
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def estimate_future(self):
|
||||||
|
"""
|
||||||
|
For all Invoices estimate upcoming postings
|
||||||
|
"""
|
||||||
|
for x in self.deferred_invoices:
|
||||||
|
x.estimate_future()
|
||||||
|
|
||||||
|
def calculate_revenue_and_expense(self):
|
||||||
|
"""
|
||||||
|
calculate the deferred revenue/expense for all invoices
|
||||||
|
"""
|
||||||
|
# initialize period_total list for report
|
||||||
|
for period in self.period_list:
|
||||||
|
self.period_total.append(frappe._dict({"key": period.key, "total": 0, "actual": 0}))
|
||||||
|
|
||||||
|
for inv in self.deferred_invoices:
|
||||||
|
inv_total = inv.calculate_invoice_revenue_expense_for_period()
|
||||||
|
# calculate total for whole report
|
||||||
|
for idx, period in enumerate(self.period_list, 0):
|
||||||
|
self.period_total[idx].total += inv_total[idx].total
|
||||||
|
self.period_total[idx].actual += inv_total[idx].actual
|
||||||
|
|
||||||
|
def get_columns(self):
|
||||||
|
columns = []
|
||||||
|
columns.append({"label": _("Name"), "fieldname": "name", "fieldtype": "Data", "read_only": 1})
|
||||||
|
for period in self.period_list:
|
||||||
|
columns.append(
|
||||||
|
{
|
||||||
|
"label": _(period.label),
|
||||||
|
"fieldname": period.key,
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"read_only": 1,
|
||||||
|
})
|
||||||
|
return columns
|
||||||
|
|
||||||
|
def generate_report_data(self):
|
||||||
|
"""
|
||||||
|
Generate report data for all invoices. Adds total rows for revenue and expense
|
||||||
|
"""
|
||||||
|
ret = []
|
||||||
|
|
||||||
|
for inv in self.deferred_invoices:
|
||||||
|
ret += inv.report_data()
|
||||||
|
|
||||||
|
# empty row for padding
|
||||||
|
ret += [{}]
|
||||||
|
|
||||||
|
# add total row
|
||||||
|
if ret is not []:
|
||||||
|
if self.filters.type == "Revenue":
|
||||||
|
total_row = frappe._dict({"name": "Total Deferred Income"})
|
||||||
|
elif self.filters.type == "Expense":
|
||||||
|
total_row = frappe._dict({"name": "Total Deferred Expense"})
|
||||||
|
|
||||||
|
for idx, period in enumerate(self.period_list, 0):
|
||||||
|
total_row[period.key] = self.period_total[idx].total
|
||||||
|
ret.append(total_row)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def prepare_chart(self):
|
||||||
|
chart = {
|
||||||
|
"data": {
|
||||||
|
"labels": [period.label for period in self.period_list],
|
||||||
|
"datasets": [
|
||||||
|
{
|
||||||
|
"name": "Actual Posting",
|
||||||
|
"chartType": "bar",
|
||||||
|
"values": [x.actual for x in self.period_total],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"type": "axis-mixed",
|
||||||
|
"height": 500,
|
||||||
|
"axisOptions": {"xAxisMode": "Tick", "xIsSeries": True},
|
||||||
|
"barOptions": {"stacked": False, "spaceRatio": 0.5},
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.filters.with_upcoming_postings:
|
||||||
|
chart["data"]["datasets"].append({
|
||||||
|
"name": "Expected",
|
||||||
|
"chartType": "line",
|
||||||
|
"values": [x.total for x in self.period_total]
|
||||||
|
})
|
||||||
|
|
||||||
|
return chart
|
||||||
|
|
||||||
|
def run(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Run report and generate data
|
||||||
|
"""
|
||||||
|
self.deferred_invoices.clear()
|
||||||
|
self.get_period_list()
|
||||||
|
self.get_invoices()
|
||||||
|
|
||||||
|
if self.filters.with_upcoming_postings:
|
||||||
|
self.estimate_future()
|
||||||
|
self.calculate_revenue_and_expense()
|
||||||
|
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
report = Deferred_Revenue_and_Expense_Report(filters=filters)
|
||||||
|
report.run()
|
||||||
|
|
||||||
|
columns = report.get_columns()
|
||||||
|
data = report.generate_report_data()
|
||||||
|
message = []
|
||||||
|
chart = report.prepare_chart()
|
||||||
|
|
||||||
|
return columns, data, message, chart
|
@ -0,0 +1,253 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import qb
|
||||||
|
from frappe.utils import nowdate
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.account.test_account import create_account
|
||||||
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
from erpnext.accounts.report.deferred_revenue_and_expense.deferred_revenue_and_expense import (
|
||||||
|
Deferred_Revenue_and_Expense_Report,
|
||||||
|
)
|
||||||
|
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||||
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
|
|
||||||
|
|
||||||
|
class TestDeferredRevenueAndExpense(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(self):
|
||||||
|
clear_old_entries()
|
||||||
|
create_company()
|
||||||
|
|
||||||
|
def test_deferred_revenue(self):
|
||||||
|
# created deferred expense accounts, if not found
|
||||||
|
deferred_revenue_account = create_account(
|
||||||
|
account_name="Deferred Revenue",
|
||||||
|
parent_account="Current Liabilities - _CD",
|
||||||
|
company="_Test Company DR",
|
||||||
|
)
|
||||||
|
|
||||||
|
acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
|
||||||
|
acc_settings.book_deferred_entries_based_on = "Months"
|
||||||
|
acc_settings.save()
|
||||||
|
|
||||||
|
customer = frappe.new_doc("Customer")
|
||||||
|
customer.customer_name = "_Test Customer DR"
|
||||||
|
customer.type = "Individual"
|
||||||
|
customer.insert()
|
||||||
|
|
||||||
|
item = create_item(
|
||||||
|
"_Test Internet Subscription",
|
||||||
|
is_stock_item=0,
|
||||||
|
warehouse="All Warehouses - _CD",
|
||||||
|
company="_Test Company DR",
|
||||||
|
)
|
||||||
|
item.enable_deferred_revenue = 1
|
||||||
|
item.deferred_revenue_account = deferred_revenue_account
|
||||||
|
item.no_of_months = 3
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
si = create_sales_invoice(
|
||||||
|
item=item.name,
|
||||||
|
company="_Test Company DR",
|
||||||
|
customer="_Test Customer DR",
|
||||||
|
debit_to="Debtors - _CD",
|
||||||
|
posting_date="2021-05-01",
|
||||||
|
parent_cost_center="Main - _CD",
|
||||||
|
cost_center="Main - _CD",
|
||||||
|
do_not_submit=True,
|
||||||
|
rate=300,
|
||||||
|
price_list_rate=300,
|
||||||
|
)
|
||||||
|
si.items[0].enable_deferred_revenue = 1
|
||||||
|
si.items[0].service_start_date = "2021-05-01"
|
||||||
|
si.items[0].service_end_date = "2021-08-01"
|
||||||
|
si.items[0].deferred_revenue_account = deferred_revenue_account
|
||||||
|
si.items[0].income_account = "Sales - _CD"
|
||||||
|
si.save()
|
||||||
|
si.submit()
|
||||||
|
|
||||||
|
pda = frappe.get_doc(
|
||||||
|
dict(
|
||||||
|
doctype="Process Deferred Accounting",
|
||||||
|
posting_date=nowdate(),
|
||||||
|
start_date="2021-05-01",
|
||||||
|
end_date="2021-08-01",
|
||||||
|
type="Income",
|
||||||
|
company="_Test Company DR",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
pda.insert()
|
||||||
|
pda.submit()
|
||||||
|
|
||||||
|
# execute report
|
||||||
|
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
||||||
|
self.filters = frappe._dict(
|
||||||
|
{
|
||||||
|
"company": frappe.defaults.get_user_default("Company"),
|
||||||
|
"filter_based_on": "Date Range",
|
||||||
|
"period_start_date": "2021-05-01",
|
||||||
|
"period_end_date": "2021-08-01",
|
||||||
|
"from_fiscal_year": fiscal_year.year,
|
||||||
|
"to_fiscal_year": fiscal_year.year,
|
||||||
|
"periodicity": "Monthly",
|
||||||
|
"type": "Revenue",
|
||||||
|
"with_upcoming_postings": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
report = Deferred_Revenue_and_Expense_Report(filters=self.filters)
|
||||||
|
report.run()
|
||||||
|
expected = [
|
||||||
|
{"key": "may_2021", "total": 100.0, "actual": 100.0},
|
||||||
|
{"key": "jun_2021", "total": 100.0, "actual": 100.0},
|
||||||
|
{"key": "jul_2021", "total": 100.0, "actual": 100.0},
|
||||||
|
{"key": "aug_2021", "total": 0, "actual": 0},
|
||||||
|
]
|
||||||
|
self.assertEqual(report.period_total, expected)
|
||||||
|
|
||||||
|
def test_deferred_expense(self):
|
||||||
|
# created deferred expense accounts, if not found
|
||||||
|
deferred_expense_account = create_account(
|
||||||
|
account_name="Deferred Expense",
|
||||||
|
parent_account="Current Assets - _CD",
|
||||||
|
company="_Test Company DR",
|
||||||
|
)
|
||||||
|
|
||||||
|
acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
|
||||||
|
acc_settings.book_deferred_entries_based_on = "Months"
|
||||||
|
acc_settings.save()
|
||||||
|
|
||||||
|
supplier = create_supplier(
|
||||||
|
supplier_name="_Test Furniture Supplier", supplier_group="Local", supplier_type="Company"
|
||||||
|
)
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
item = create_item(
|
||||||
|
"_Test Office Desk",
|
||||||
|
is_stock_item=0,
|
||||||
|
warehouse="All Warehouses - _CD",
|
||||||
|
company="_Test Company DR",
|
||||||
|
)
|
||||||
|
item.enable_deferred_expense = 1
|
||||||
|
item.deferred_expense_account = deferred_expense_account
|
||||||
|
item.no_of_months_exp = 3
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
pi = make_purchase_invoice(
|
||||||
|
item=item.name,
|
||||||
|
company="_Test Company DR",
|
||||||
|
supplier="_Test Furniture Supplier",
|
||||||
|
is_return=False,
|
||||||
|
update_stock=False,
|
||||||
|
posting_date=frappe.utils.datetime.date(2021, 5, 1),
|
||||||
|
parent_cost_center="Main - _CD",
|
||||||
|
cost_center="Main - _CD",
|
||||||
|
do_not_save=True,
|
||||||
|
rate=300,
|
||||||
|
price_list_rate=300,
|
||||||
|
warehouse="All Warehouses - _CD",
|
||||||
|
qty=1,
|
||||||
|
)
|
||||||
|
pi.set_posting_time = True
|
||||||
|
pi.items[0].enable_deferred_expense = 1
|
||||||
|
pi.items[0].service_start_date = "2021-05-01"
|
||||||
|
pi.items[0].service_end_date = "2021-08-01"
|
||||||
|
pi.items[0].deferred_expense_account = deferred_expense_account
|
||||||
|
pi.items[0].expense_account = "Office Maintenance Expenses - _CD"
|
||||||
|
pi.save()
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
pda = frappe.get_doc(
|
||||||
|
dict(
|
||||||
|
doctype="Process Deferred Accounting",
|
||||||
|
posting_date=nowdate(),
|
||||||
|
start_date="2021-05-01",
|
||||||
|
end_date="2021-08-01",
|
||||||
|
type="Expense",
|
||||||
|
company="_Test Company DR",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
pda.insert()
|
||||||
|
pda.submit()
|
||||||
|
|
||||||
|
# execute report
|
||||||
|
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
||||||
|
self.filters = frappe._dict(
|
||||||
|
{
|
||||||
|
"company": frappe.defaults.get_user_default("Company"),
|
||||||
|
"filter_based_on": "Date Range",
|
||||||
|
"period_start_date": "2021-05-01",
|
||||||
|
"period_end_date": "2021-08-01",
|
||||||
|
"from_fiscal_year": fiscal_year.year,
|
||||||
|
"to_fiscal_year": fiscal_year.year,
|
||||||
|
"periodicity": "Monthly",
|
||||||
|
"type": "Expense",
|
||||||
|
"with_upcoming_postings": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
report = Deferred_Revenue_and_Expense_Report(filters=self.filters)
|
||||||
|
report.run()
|
||||||
|
expected = [
|
||||||
|
{"key": "may_2021", "total": -100.0, "actual": -100.0},
|
||||||
|
{"key": "jun_2021", "total": -100.0, "actual": -100.0},
|
||||||
|
{"key": "jul_2021", "total": -100.0, "actual": -100.0},
|
||||||
|
{"key": "aug_2021", "total": 0, "actual": 0},
|
||||||
|
]
|
||||||
|
self.assertEqual(report.period_total, expected)
|
||||||
|
|
||||||
|
|
||||||
|
def create_company():
|
||||||
|
company = frappe.db.exists("Company", "_Test Company DR")
|
||||||
|
if not company:
|
||||||
|
company = frappe.new_doc("Company")
|
||||||
|
company.company_name = "_Test Company DR"
|
||||||
|
company.default_currency = "INR"
|
||||||
|
company.chart_of_accounts = "Standard"
|
||||||
|
company.insert()
|
||||||
|
|
||||||
|
|
||||||
|
def clear_old_entries():
|
||||||
|
item = qb.DocType("Item")
|
||||||
|
account = qb.DocType("Account")
|
||||||
|
customer = qb.DocType("Customer")
|
||||||
|
supplier = qb.DocType("Supplier")
|
||||||
|
sinv = qb.DocType("Sales Invoice")
|
||||||
|
sinv_item = qb.DocType("Sales Invoice Item")
|
||||||
|
pinv = qb.DocType("Purchase Invoice")
|
||||||
|
pinv_item = qb.DocType("Purchase Invoice Item")
|
||||||
|
|
||||||
|
qb.from_(account).delete().where(
|
||||||
|
(account.account_name == "Deferred Revenue")
|
||||||
|
| (account.account_name == "Deferred Expense") & (account.company == "_Test Company DR")
|
||||||
|
).run()
|
||||||
|
qb.from_(item).delete().where(
|
||||||
|
(item.item_code == "_Test Internet Subscription") | (item.item_code == "_Test Office Rent")
|
||||||
|
).run()
|
||||||
|
qb.from_(customer).delete().where(customer.customer_name == "_Test Customer DR").run()
|
||||||
|
qb.from_(supplier).delete().where(supplier.supplier_name == "_Test Furniture Supplier").run()
|
||||||
|
|
||||||
|
# delete existing invoices with deferred items
|
||||||
|
deferred_invoices = (
|
||||||
|
qb.from_(sinv)
|
||||||
|
.join(sinv_item)
|
||||||
|
.on(sinv.name == sinv_item.parent)
|
||||||
|
.select(sinv.name)
|
||||||
|
.where(sinv_item.enable_deferred_revenue == 1)
|
||||||
|
.run()
|
||||||
|
)
|
||||||
|
if deferred_invoices:
|
||||||
|
qb.from_(sinv).delete().where(sinv.name.isin(deferred_invoices)).run()
|
||||||
|
|
||||||
|
deferred_invoices = (
|
||||||
|
qb.from_(pinv)
|
||||||
|
.join(pinv_item)
|
||||||
|
.on(pinv.name == pinv_item.parent)
|
||||||
|
.select(pinv.name)
|
||||||
|
.where(pinv_item.enable_deferred_expense == 1)
|
||||||
|
.run()
|
||||||
|
)
|
||||||
|
if deferred_invoices:
|
||||||
|
qb.from_(pinv).delete().where(pinv.name.isin(deferred_invoices)).run()
|
@ -495,7 +495,6 @@ class Asset(AccountsController):
|
|||||||
|
|
||||||
asset_value_after_full_schedule = flt(
|
asset_value_after_full_schedule = flt(
|
||||||
flt(self.gross_purchase_amount) -
|
flt(self.gross_purchase_amount) -
|
||||||
flt(self.opening_accumulated_depreciation) -
|
|
||||||
flt(accumulated_depreciation_after_full_schedule), self.precision('gross_purchase_amount'))
|
flt(accumulated_depreciation_after_full_schedule), self.precision('gross_purchase_amount'))
|
||||||
|
|
||||||
if (row.expected_value_after_useful_life and
|
if (row.expected_value_after_useful_life and
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/asset",
|
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/asset",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"modified": "2021-08-24 17:50:41.573281",
|
"modified": "2021-12-02 11:24:37.963746",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Assets",
|
"name": "Assets",
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"is_single": 0,
|
"is_single": 0,
|
||||||
"is_skipped": 0,
|
"is_skipped": 0,
|
||||||
"modified": "2021-08-24 12:49:37.665239",
|
"modified": "2021-11-23 10:02:03.242127",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Asset Category",
|
"name": "Asset Category",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
{
|
{
|
||||||
"action": "Show Form Tour",
|
"action": "Create Entry",
|
||||||
"action_label": "Let's create a new Asset item",
|
"action_label": "Let's create a new Asset item",
|
||||||
"creation": "2021-08-13 14:27:07.277167",
|
"creation": "2021-08-13 14:27:07.277167",
|
||||||
"description": "# Asset Item\n\nAsset items are created based on Asset Category. You can create one or multiple items against once Asset Category. The sales and purchase transaction for Asset is done via Asset Item. ",
|
"description": "# Asset Item\n\nAsset items are created based on Asset Category. You can create one or multiple items against once Asset Category. The sales and purchase transaction for Asset is done via Asset Item. ",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Onboarding Step",
|
"doctype": "Onboarding Step",
|
||||||
|
"form_tour": "Item",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"is_single": 0,
|
"is_single": 0,
|
||||||
"is_skipped": 0,
|
"is_skipped": 0,
|
||||||
"modified": "2021-08-16 13:59:18.362233",
|
"modified": "2021-12-02 11:23:48.158504",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Asset Item",
|
"name": "Asset Item",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"reference_document": "Item",
|
"reference_document": "Item",
|
||||||
"show_form_tour": 0,
|
"show_form_tour": 1,
|
||||||
"show_full_form": 0,
|
"show_full_form": 1,
|
||||||
"title": "Create an Asset Item",
|
"title": "Create an Asset Item",
|
||||||
"validate_action": 1
|
"validate_action": 1
|
||||||
}
|
}
|
@ -9,7 +9,7 @@
|
|||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"is_single": 0,
|
"is_single": 0,
|
||||||
"is_skipped": 0,
|
"is_skipped": 0,
|
||||||
"modified": "2021-08-24 17:26:57.180637",
|
"modified": "2021-11-23 10:02:03.235498",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Asset Purchase",
|
"name": "Asset Purchase",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"is_single": 0,
|
"is_single": 0,
|
||||||
"is_skipped": 0,
|
"is_skipped": 0,
|
||||||
"modified": "2021-08-24 17:46:37.646174",
|
"modified": "2021-11-23 10:02:03.229566",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Fixed Asset Accounts",
|
"name": "Fixed Asset Accounts",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
@ -124,6 +124,14 @@ frappe.ui.form.on("Request for Quotation",{
|
|||||||
dialog.show()
|
dialog.show()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
schedule_date(frm) {
|
||||||
|
if(frm.doc.schedule_date){
|
||||||
|
frm.doc.items.forEach((item) => {
|
||||||
|
item.schedule_date = frm.doc.schedule_date;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
refresh_field("items");
|
||||||
|
},
|
||||||
preview: (frm) => {
|
preview: (frm) => {
|
||||||
let dialog = new frappe.ui.Dialog({
|
let dialog = new frappe.ui.Dialog({
|
||||||
title: __('Preview Email'),
|
title: __('Preview Email'),
|
||||||
@ -184,7 +192,13 @@ frappe.ui.form.on("Request for Quotation",{
|
|||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
frappe.ui.form.on("Request for Quotation Item", {
|
||||||
|
items_add(frm, cdt, cdn) {
|
||||||
|
if (frm.doc.schedule_date) {
|
||||||
|
frappe.model.set_value(cdt, cdn, 'schedule_date', frm.doc.schedule_date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
frappe.ui.form.on("Request for Quotation Supplier",{
|
frappe.ui.form.on("Request for Quotation Supplier",{
|
||||||
supplier: function(frm, cdt, cdn) {
|
supplier: function(frm, cdt, cdn) {
|
||||||
var d = locals[cdt][cdn]
|
var d = locals[cdt][cdn]
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
"vendor",
|
"vendor",
|
||||||
"column_break1",
|
"column_break1",
|
||||||
"transaction_date",
|
"transaction_date",
|
||||||
|
"schedule_date",
|
||||||
"status",
|
"status",
|
||||||
"amended_from",
|
"amended_from",
|
||||||
"suppliers_section",
|
"suppliers_section",
|
||||||
@ -246,16 +247,22 @@
|
|||||||
"fieldname": "sec_break_email_2",
|
"fieldname": "sec_break_email_2",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hide_border": 1
|
"hide_border": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "schedule_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Required Date"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-shopping-cart",
|
"icon": "fa fa-shopping-cart",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-11-05 22:04:29.017134",
|
"modified": "2021-11-24 17:47:49.909000",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Request for Quotation",
|
"name": "Request for Quotation",
|
||||||
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
|
@ -19,7 +19,7 @@ class TestExitInterview(unittest.TestCase):
|
|||||||
frappe.db.sql('delete from `tabExit Interview`')
|
frappe.db.sql('delete from `tabExit Interview`')
|
||||||
|
|
||||||
def test_duplicate_interview(self):
|
def test_duplicate_interview(self):
|
||||||
employee = make_employee('employeeexit1@example.com')
|
employee = make_employee('employeeexitint1@example.com')
|
||||||
frappe.db.set_value('Employee', employee, 'relieving_date', getdate())
|
frappe.db.set_value('Employee', employee, 'relieving_date', getdate())
|
||||||
interview = create_exit_interview(employee)
|
interview = create_exit_interview(employee)
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ class TestExitInterview(unittest.TestCase):
|
|||||||
self.assertRaises(frappe.DuplicateEntryError, doc.save)
|
self.assertRaises(frappe.DuplicateEntryError, doc.save)
|
||||||
|
|
||||||
def test_relieving_date_validation(self):
|
def test_relieving_date_validation(self):
|
||||||
employee = make_employee('employeeexit2@example.com')
|
employee = make_employee('employeeexitint2@example.com')
|
||||||
# unset relieving date
|
# unset relieving date
|
||||||
frappe.db.set_value('Employee', employee, 'relieving_date', None)
|
frappe.db.set_value('Employee', employee, 'relieving_date', None)
|
||||||
|
|
||||||
|
@ -108,12 +108,11 @@ def get_data(filters):
|
|||||||
interview.status.as_('interview_status'), interview.employee_status.as_('employee_status'),
|
interview.status.as_('interview_status'), interview.employee_status.as_('employee_status'),
|
||||||
interview.reference_document_name.as_('questionnaire'), fnf.name.as_('full_and_final_statement'))
|
interview.reference_document_name.as_('questionnaire'), fnf.name.as_('full_and_final_statement'))
|
||||||
.distinct()
|
.distinct()
|
||||||
.orderby(employee.relieving_date, order=Order.asc)
|
|
||||||
.where(
|
.where(
|
||||||
((employee.relieving_date.isnotnull()) | (employee.relieving_date != ''))
|
((employee.relieving_date.isnotnull()) | (employee.relieving_date != ''))
|
||||||
& ((interview.name.isnull()) | ((interview.name.isnotnull()) & (interview.docstatus != 2)))
|
& ((interview.name.isnull()) | ((interview.name.isnotnull()) & (interview.docstatus != 2)))
|
||||||
& ((fnf.name.isnull()) | ((fnf.name.isnotnull()) & (fnf.docstatus != 2)))
|
& ((fnf.name.isnull()) | ((fnf.name.isnotnull()) & (fnf.docstatus != 2)))
|
||||||
)
|
).orderby(employee.relieving_date, order=Order.asc)
|
||||||
)
|
)
|
||||||
|
|
||||||
query = get_conditions(filters, query, employee, interview, fnf)
|
query = get_conditions(filters, query, employee, interview, fnf)
|
||||||
|
@ -56,9 +56,14 @@ class TestMaintenanceSchedule(unittest.TestCase):
|
|||||||
|
|
||||||
ms.submit()
|
ms.submit()
|
||||||
s_id = ms.get_pending_data(data_type = "id", item_name = i.item_name, s_date = expected_dates[1])
|
s_id = ms.get_pending_data(data_type = "id", item_name = i.item_name, s_date = expected_dates[1])
|
||||||
test = make_maintenance_visit(source_name = ms.name, item_name = "_Test Item", s_id = s_id)
|
|
||||||
|
# Check if item is mapped in visit.
|
||||||
|
test_map_visit = make_maintenance_visit(source_name = ms.name, item_name = "_Test Item", s_id = s_id)
|
||||||
|
self.assertEqual(len(test_map_visit.purposes), 1)
|
||||||
|
self.assertEqual(test_map_visit.purposes[0].item_name, "_Test Item")
|
||||||
|
|
||||||
visit = frappe.new_doc('Maintenance Visit')
|
visit = frappe.new_doc('Maintenance Visit')
|
||||||
visit = test
|
visit = test_map_visit
|
||||||
visit.maintenance_schedule = ms.name
|
visit.maintenance_schedule = ms.name
|
||||||
visit.maintenance_schedule_detail = s_id
|
visit.maintenance_schedule_detail = s_id
|
||||||
visit.completion_status = "Partially Completed"
|
visit.completion_status = "Partially Completed"
|
||||||
|
@ -47,7 +47,7 @@ frappe.ui.form.on('Maintenance Visit', {
|
|||||||
frm.set_value({ status: 'Draft' });
|
frm.set_value({ status: 'Draft' });
|
||||||
}
|
}
|
||||||
if (frm.doc.__islocal) {
|
if (frm.doc.__islocal) {
|
||||||
frm.clear_table("purposes");
|
frm.doc.maintenance_type == 'Unscheduled' && frm.clear_table("purposes");
|
||||||
frm.set_value({ mntc_date: frappe.datetime.get_today() });
|
frm.set_value({ mntc_date: frappe.datetime.get_today() });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import add_months, today
|
from frappe.utils import add_months, today
|
||||||
|
|
||||||
from erpnext import get_company_currency
|
from erpnext import get_company_currency
|
||||||
|
from erpnext.tests.utils import ERPNextTestCase
|
||||||
|
|
||||||
from .blanket_order import make_order
|
from .blanket_order import make_order
|
||||||
|
|
||||||
|
|
||||||
class TestBlanketOrder(unittest.TestCase):
|
class TestBlanketOrder(ERPNextTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
frappe.flags.args = frappe._dict()
|
frappe.flags.args = frappe._dict()
|
||||||
|
|
||||||
|
@ -922,7 +922,7 @@ def validate_bom_no(item, bom_no):
|
|||||||
rm_item_exists = True
|
rm_item_exists = True
|
||||||
if bom.item.lower() == item.lower() or \
|
if bom.item.lower() == item.lower() or \
|
||||||
bom.item.lower() == cstr(frappe.db.get_value("Item", item, "variant_of")).lower():
|
bom.item.lower() == cstr(frappe.db.get_value("Item", item, "variant_of")).lower():
|
||||||
rm_item_exists = True
|
rm_item_exists = True
|
||||||
if not rm_item_exists:
|
if not rm_item_exists:
|
||||||
frappe.throw(_("BOM {0} does not belong to Item {1}").format(bom_no, item))
|
frappe.throw(_("BOM {0} does not belong to Item {1}").format(bom_no, item))
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
@ -18,10 +17,11 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import
|
|||||||
create_stock_reconciliation,
|
create_stock_reconciliation,
|
||||||
)
|
)
|
||||||
from erpnext.tests.test_subcontracting import set_backflush_based_on
|
from erpnext.tests.test_subcontracting import set_backflush_based_on
|
||||||
|
from erpnext.tests.utils import ERPNextTestCase
|
||||||
|
|
||||||
test_records = frappe.get_test_records('BOM')
|
test_records = frappe.get_test_records('BOM')
|
||||||
|
|
||||||
class TestBOM(unittest.TestCase):
|
class TestBOM(ERPNextTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
if not frappe.get_value('Item', '_Test Item'):
|
if not frappe.get_value('Item', '_Test Item'):
|
||||||
make_test_records('Item')
|
make_test_records('Item')
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
"col_break1",
|
"col_break1",
|
||||||
"workstation",
|
"workstation",
|
||||||
"time_in_mins",
|
"time_in_mins",
|
||||||
|
"fixed_time",
|
||||||
"costing_section",
|
"costing_section",
|
||||||
"hour_rate",
|
"hour_rate",
|
||||||
"base_hour_rate",
|
"base_hour_rate",
|
||||||
@ -79,6 +80,14 @@
|
|||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Operation time does not depend on quantity to produce",
|
||||||
|
"fieldname": "fixed_time",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Fixed Time"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "operating_cost",
|
"fieldname": "operating_cost",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
@ -177,12 +186,13 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-09-13 16:45:01.092868",
|
"modified": "2021-12-15 03:00:00.473173",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM Operation",
|
"name": "BOM Operation",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
}
|
}
|
@ -1,19 +1,16 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost
|
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost
|
||||||
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||||
from erpnext.stock.doctype.item.test_item import create_item
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
|
from erpnext.tests.utils import ERPNextTestCase
|
||||||
|
|
||||||
test_records = frappe.get_test_records('BOM')
|
test_records = frappe.get_test_records('BOM')
|
||||||
|
|
||||||
class TestBOMUpdateTool(unittest.TestCase):
|
class TestBOMUpdateTool(ERPNextTestCase):
|
||||||
def test_replace_bom(self):
|
def test_replace_bom(self):
|
||||||
current_bom = "BOM-_Test Item Home Desktop Manufactured-001"
|
current_bom = "BOM-_Test Item Home Desktop Manufactured-001"
|
||||||
|
|
||||||
|
@ -75,6 +75,32 @@ frappe.ui.form.on('Job Card', {
|
|||||||
&& (frm.doc.items || !frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) {
|
&& (frm.doc.items || !frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) {
|
||||||
frm.trigger("prepare_timer_buttons");
|
frm.trigger("prepare_timer_buttons");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frm.trigger("setup_quality_inspection");
|
||||||
|
if (frm.doc.work_order) {
|
||||||
|
frappe.db.get_value('Work Order', frm.doc.work_order,
|
||||||
|
'transfer_material_against').then((r) => {
|
||||||
|
if (r.message.transfer_material_against == 'Work Order') {
|
||||||
|
frm.set_df_property('items', 'hidden', 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setup_quality_inspection: function(frm) {
|
||||||
|
let quality_inspection_field = frm.get_docfield("quality_inspection");
|
||||||
|
quality_inspection_field.get_route_options_for_new_doc = function(frm) {
|
||||||
|
return {
|
||||||
|
"inspection_type": "In Process",
|
||||||
|
"reference_type": "Job Card",
|
||||||
|
"reference_name": frm.doc.name,
|
||||||
|
"item_code": frm.doc.production_item,
|
||||||
|
"item_name": frm.doc.item_name,
|
||||||
|
"item_serial_no": frm.doc.serial_no,
|
||||||
|
"batch_no": frm.doc.batch_no,
|
||||||
|
"quality_inspection_template": frm.doc.quality_inspection_template,
|
||||||
|
};
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
setup_corrective_job_card: function(frm) {
|
setup_corrective_job_card: function(frm) {
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"serial_no",
|
"serial_no",
|
||||||
"column_break_12",
|
"column_break_12",
|
||||||
"wip_warehouse",
|
"wip_warehouse",
|
||||||
|
"quality_inspection_template",
|
||||||
"quality_inspection",
|
"quality_inspection",
|
||||||
"project",
|
"project",
|
||||||
"batch_no",
|
"batch_no",
|
||||||
@ -408,11 +409,18 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Job Card Scrap Item",
|
"options": "Job Card Scrap Item",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "operation.quality_inspection_template",
|
||||||
|
"fieldname": "quality_inspection_template",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Quality Inspection Template",
|
||||||
|
"options": "Quality Inspection Template"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-11-12 10:15:03.572401",
|
"modified": "2021-11-24 19:17:40.879235",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Job Card",
|
"name": "Job Card",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
import unittest
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import random_string
|
from frappe.utils import random_string
|
||||||
@ -12,9 +11,10 @@ from erpnext.manufacturing.doctype.job_card.job_card import (
|
|||||||
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
|
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
|
||||||
from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
|
from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
|
from erpnext.tests.utils import ERPNextTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestJobCard(unittest.TestCase):
|
class TestJobCard(ERPNextTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
make_bom_for_jc_tests()
|
make_bom_for_jc_tests()
|
||||||
|
|
||||||
@ -329,4 +329,4 @@ def make_bom_for_jc_tests():
|
|||||||
bom.rm_cost_as_per = "Valuation Rate"
|
bom.rm_cost_as_per = "Valuation Rate"
|
||||||
bom.items[0].uom = "_Test UOM 1"
|
bom.items[0].uom = "_Test UOM 1"
|
||||||
bom.items[0].conversion_factor = 5
|
bom.items[0].conversion_factor = 5
|
||||||
bom.insert()
|
bom.insert()
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"is_corrective_operation",
|
"is_corrective_operation",
|
||||||
"job_card_section",
|
"job_card_section",
|
||||||
"create_job_card_based_on_batch_size",
|
"create_job_card_based_on_batch_size",
|
||||||
|
"quality_inspection_template",
|
||||||
"column_break_6",
|
"column_break_6",
|
||||||
"batch_size",
|
"batch_size",
|
||||||
"sub_operations_section",
|
"sub_operations_section",
|
||||||
@ -92,15 +93,22 @@
|
|||||||
"fieldname": "is_corrective_operation",
|
"fieldname": "is_corrective_operation",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Is Corrective Operation"
|
"label": "Is Corrective Operation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "quality_inspection_template",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Quality Inspection Template",
|
||||||
|
"options": "Quality Inspection Template"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-wrench",
|
"icon": "fa fa-wrench",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-01-12 15:09:23.593338",
|
"modified": "2021-11-24 19:15:24.357187",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Operation",
|
"name": "Operation",
|
||||||
|
"naming_rule": "Set by user",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import add_to_date, flt, now_datetime, nowdate
|
from frappe.utils import add_to_date, flt, now_datetime, nowdate
|
||||||
|
|
||||||
@ -17,9 +14,10 @@ from erpnext.stock.doctype.item.test_item import create_item
|
|||||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
|
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
|
||||||
create_stock_reconciliation,
|
create_stock_reconciliation,
|
||||||
)
|
)
|
||||||
|
from erpnext.tests.utils import ERPNextTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestProductionPlan(unittest.TestCase):
|
class TestProductionPlan(ERPNextTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
for item in ['Test Production Item 1', 'Subassembly Item 1',
|
for item in ['Test Production Item 1', 'Subassembly Item 1',
|
||||||
'Raw Material Item 1', 'Raw Material Item 2']:
|
'Raw Material Item 1', 'Raw Material Item 2']:
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.test_runner import make_test_records
|
from frappe.test_runner import make_test_records
|
||||||
|
|
||||||
from erpnext.manufacturing.doctype.job_card.job_card import OperationSequenceError
|
from erpnext.manufacturing.doctype.job_card.job_card import OperationSequenceError
|
||||||
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
|
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
|
||||||
from erpnext.stock.doctype.item.test_item import make_item
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
from erpnext.tests.utils import ERPNextTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestRouting(unittest.TestCase):
|
class TestRouting(ERPNextTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.item_code = "Test Routing Item - A"
|
cls.item_code = "Test Routing Item - A"
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
import unittest
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import add_months, cint, flt, now, today
|
from frappe.utils import add_months, cint, flt, now, today
|
||||||
@ -95,7 +94,7 @@ class TestWorkOrder(ERPNextTestCase):
|
|||||||
|
|
||||||
def test_reserved_qty_for_partial_completion(self):
|
def test_reserved_qty_for_partial_completion(self):
|
||||||
item = "_Test Item"
|
item = "_Test Item"
|
||||||
warehouse = create_warehouse("Test Warehouse for reserved_qty - _TC")
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
|
||||||
bin1_at_start = get_bin(item, warehouse)
|
bin1_at_start = get_bin(item, warehouse)
|
||||||
|
|
||||||
@ -847,6 +846,45 @@ class TestWorkOrder(ERPNextTestCase):
|
|||||||
close_work_order(wo_order, "Closed")
|
close_work_order(wo_order, "Closed")
|
||||||
self.assertEqual(wo_order.get('status'), "Closed")
|
self.assertEqual(wo_order.get('status'), "Closed")
|
||||||
|
|
||||||
|
def test_fix_time_operations(self):
|
||||||
|
bom = frappe.get_doc({
|
||||||
|
"doctype": "BOM",
|
||||||
|
"item": "_Test FG Item 2",
|
||||||
|
"is_active": 1,
|
||||||
|
"is_default": 1,
|
||||||
|
"quantity": 1.0,
|
||||||
|
"with_operations": 1,
|
||||||
|
"operations": [
|
||||||
|
{
|
||||||
|
"operation": "_Test Operation 1",
|
||||||
|
"description": "_Test",
|
||||||
|
"workstation": "_Test Workstation 1",
|
||||||
|
"time_in_mins": 60,
|
||||||
|
"operating_cost": 140,
|
||||||
|
"fixed_time": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"amount": 5000.0,
|
||||||
|
"doctype": "BOM Item",
|
||||||
|
"item_code": "_Test Item",
|
||||||
|
"parentfield": "items",
|
||||||
|
"qty": 1.0,
|
||||||
|
"rate": 5000.0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
bom.save()
|
||||||
|
bom.submit()
|
||||||
|
|
||||||
|
|
||||||
|
wo1 = make_wo_order_test_record(item=bom.item, bom_no=bom.name, qty=1, skip_transfer=1, do_not_submit=1)
|
||||||
|
wo2 = make_wo_order_test_record(item=bom.item, bom_no=bom.name, qty=2, skip_transfer=1, do_not_submit=1)
|
||||||
|
|
||||||
|
self.assertEqual(wo1.operations[0].time_in_mins, wo2.operations[0].time_in_mins)
|
||||||
|
|
||||||
|
|
||||||
def update_job_card(job_card):
|
def update_job_card(job_card):
|
||||||
job_card_doc = frappe.get_doc('Job Card', job_card)
|
job_card_doc = frappe.get_doc('Job Card', job_card)
|
||||||
job_card_doc.set('scrap_items', [
|
job_card_doc.set('scrap_items', [
|
||||||
|
@ -505,16 +505,19 @@ class WorkOrder(Document):
|
|||||||
"""Fetch operations from BOM and set in 'Work Order'"""
|
"""Fetch operations from BOM and set in 'Work Order'"""
|
||||||
|
|
||||||
def _get_operations(bom_no, qty=1):
|
def _get_operations(bom_no, qty=1):
|
||||||
return frappe.db.sql(
|
data = frappe.get_all("BOM Operation",
|
||||||
f"""select
|
filters={"parent": bom_no},
|
||||||
operation, description, workstation, idx,
|
fields=["operation", "description", "workstation", "idx",
|
||||||
base_hour_rate as hour_rate, time_in_mins * {qty} as time_in_mins,
|
"base_hour_rate as hour_rate", "time_in_mins", "parent as bom",
|
||||||
"Pending" as status, parent as bom, batch_size, sequence_id
|
"batch_size", "sequence_id", "fixed_time"],
|
||||||
from
|
order_by="idx")
|
||||||
`tabBOM Operation`
|
|
||||||
where
|
for d in data:
|
||||||
parent = %s order by idx
|
if not d.fixed_time:
|
||||||
""", bom_no, as_dict=1)
|
d.time_in_mins = flt(d.time_in_mins) * flt(qty)
|
||||||
|
d.status = "Pending"
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
self.set('operations', [])
|
self.set('operations', [])
|
||||||
@ -542,7 +545,8 @@ class WorkOrder(Document):
|
|||||||
|
|
||||||
def calculate_time(self):
|
def calculate_time(self):
|
||||||
for d in self.get("operations"):
|
for d in self.get("operations"):
|
||||||
d.time_in_mins = flt(d.time_in_mins) * (flt(self.qty) / flt(d.batch_size))
|
if not d.fixed_time:
|
||||||
|
d.time_in_mins = flt(d.time_in_mins) * (flt(self.qty) / flt(d.batch_size))
|
||||||
|
|
||||||
self.calculate_operating_cost()
|
self.calculate_operating_cost()
|
||||||
|
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.test_runner import make_test_records
|
from frappe.test_runner import make_test_records
|
||||||
|
|
||||||
@ -13,12 +10,13 @@ from erpnext.manufacturing.doctype.workstation.workstation import (
|
|||||||
WorkstationHolidayError,
|
WorkstationHolidayError,
|
||||||
check_if_within_operating_hours,
|
check_if_within_operating_hours,
|
||||||
)
|
)
|
||||||
|
from erpnext.tests.utils import ERPNextTestCase
|
||||||
|
|
||||||
test_dependencies = ["Warehouse"]
|
test_dependencies = ["Warehouse"]
|
||||||
test_records = frappe.get_test_records('Workstation')
|
test_records = frappe.get_test_records('Workstation')
|
||||||
make_test_records('Workstation')
|
make_test_records('Workstation')
|
||||||
|
|
||||||
class TestWorkstation(unittest.TestCase):
|
class TestWorkstation(ERPNextTestCase):
|
||||||
def test_validate_timings(self):
|
def test_validate_timings(self):
|
||||||
check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 11:00:00", "2013-02-02 19:00:00")
|
check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 11:00:00", "2013-02-02 19:00:00")
|
||||||
check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 10:00:00", "2013-02-02 20:00:00")
|
check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 10:00:00", "2013-02-02 20:00:00")
|
||||||
|
@ -165,6 +165,7 @@ erpnext.patches.v12_0.set_updated_purpose_in_pick_list
|
|||||||
erpnext.patches.v12_0.set_default_payroll_based_on
|
erpnext.patches.v12_0.set_default_payroll_based_on
|
||||||
erpnext.patches.v12_0.repost_stock_ledger_entries_for_target_warehouse
|
erpnext.patches.v12_0.repost_stock_ledger_entries_for_target_warehouse
|
||||||
erpnext.patches.v12_0.update_end_date_and_status_in_email_campaign
|
erpnext.patches.v12_0.update_end_date_and_status_in_email_campaign
|
||||||
|
erpnext.patches.v13_0.validate_options_for_data_field
|
||||||
erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123
|
erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123
|
||||||
erpnext.patches.v12_0.fix_quotation_expired_status
|
erpnext.patches.v12_0.fix_quotation_expired_status
|
||||||
erpnext.patches.v12_0.rename_pos_closing_doctype
|
erpnext.patches.v12_0.rename_pos_closing_doctype
|
||||||
@ -287,7 +288,6 @@ execute:frappe.reload_doc("erpnext_integrations", "doctype", "Product Tax Catego
|
|||||||
erpnext.patches.v14_0.delete_einvoicing_doctypes
|
erpnext.patches.v14_0.delete_einvoicing_doctypes
|
||||||
erpnext.patches.v13_0.custom_fields_for_taxjar_integration #08-11-2021
|
erpnext.patches.v13_0.custom_fields_for_taxjar_integration #08-11-2021
|
||||||
erpnext.patches.v13_0.set_operation_time_based_on_operating_cost
|
erpnext.patches.v13_0.set_operation_time_based_on_operating_cost
|
||||||
erpnext.patches.v13_0.validate_options_for_data_field
|
|
||||||
erpnext.patches.v13_0.create_gst_payment_entry_fields #27-11-2021
|
erpnext.patches.v13_0.create_gst_payment_entry_fields #27-11-2021
|
||||||
erpnext.patches.v14_0.delete_shopify_doctypes
|
erpnext.patches.v14_0.delete_shopify_doctypes
|
||||||
erpnext.patches.v13_0.fix_invoice_statuses
|
erpnext.patches.v13_0.fix_invoice_statuses
|
||||||
@ -316,5 +316,5 @@ erpnext.patches.v13_0.create_ksa_vat_custom_fields
|
|||||||
erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
|
erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
|
||||||
erpnext.patches.v14_0.migrate_crm_settings
|
erpnext.patches.v14_0.migrate_crm_settings
|
||||||
erpnext.patches.v13_0.rename_ksa_qr_field
|
erpnext.patches.v13_0.rename_ksa_qr_field
|
||||||
erpnext.patches.v13_0.disable_ksa_print_format_for_others
|
erpnext.patches.v13_0.disable_ksa_print_format_for_others # 16-12-2021
|
||||||
erpnext.patches.v14_0.add_default_exit_questionnaire_notification_template
|
erpnext.patches.v14_0.add_default_exit_questionnaire_notification_template
|
@ -3,10 +3,13 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
|
from erpnext.regional.saudi_arabia.setup import add_print_formats
|
||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
company = frappe.get_all('Company', filters = {'country': 'Saudi Arabia'})
|
company = frappe.get_all('Company', filters = {'country': 'Saudi Arabia'})
|
||||||
if company:
|
if company:
|
||||||
|
add_print_formats()
|
||||||
return
|
return
|
||||||
|
|
||||||
if frappe.db.exists('DocType', 'Print Format'):
|
if frappe.db.exists('DocType', 'Print Format'):
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||||
from frappe.model.utils.rename_field import rename_field
|
from frappe.model.utils.rename_field import rename_field
|
||||||
|
|
||||||
|
|
||||||
@ -12,5 +13,20 @@ def execute():
|
|||||||
|
|
||||||
if frappe.db.exists('DocType', 'Sales Invoice'):
|
if frappe.db.exists('DocType', 'Sales Invoice'):
|
||||||
frappe.reload_doc('accounts', 'doctype', 'sales_invoice', force=True)
|
frappe.reload_doc('accounts', 'doctype', 'sales_invoice', force=True)
|
||||||
|
|
||||||
|
# rename_field method assumes that the field already exists or the doc is synced
|
||||||
|
if not frappe.db.has_column('Sales Invoice', 'ksa_einv_qr'):
|
||||||
|
create_custom_fields({
|
||||||
|
'Sales Invoice': [
|
||||||
|
dict(
|
||||||
|
fieldname='ksa_einv_qr',
|
||||||
|
label='KSA E-Invoicing QR',
|
||||||
|
fieldtype='Attach Image',
|
||||||
|
read_only=1, no_copy=1, hidden=1
|
||||||
|
)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
if frappe.db.has_column('Sales Invoice', 'qr_code'):
|
if frappe.db.has_column('Sales Invoice', 'qr_code'):
|
||||||
rename_field('Sales Invoice', 'qr_code', 'ksa_einv_qr')
|
rename_field('Sales Invoice', 'qr_code', 'ksa_einv_qr')
|
||||||
|
frappe.delete_doc_if_exists("Custom Field", "Sales Invoice-qr_code")
|
||||||
|
@ -940,10 +940,12 @@ class SalarySlip(TransactionBase):
|
|||||||
|
|
||||||
def get_amount_based_on_payment_days(self, row, joining_date, relieving_date):
|
def get_amount_based_on_payment_days(self, row, joining_date, relieving_date):
|
||||||
amount, additional_amount = row.amount, row.additional_amount
|
amount, additional_amount = row.amount, row.additional_amount
|
||||||
|
timesheet_component = frappe.db.get_value("Salary Structure", self.salary_structure, "salary_component")
|
||||||
|
|
||||||
if (self.salary_structure and
|
if (self.salary_structure and
|
||||||
cint(row.depends_on_payment_days) and cint(self.total_working_days)
|
cint(row.depends_on_payment_days) and cint(self.total_working_days)
|
||||||
and not (row.additional_salary and row.default_amount) # to identify overwritten additional salary
|
and not (row.additional_salary and row.default_amount) # to identify overwritten additional salary
|
||||||
and (not self.salary_slip_based_on_timesheet or
|
and (row.salary_component != timesheet_component or
|
||||||
getdate(self.start_date) < joining_date or
|
getdate(self.start_date) < joining_date or
|
||||||
(relieving_date and getdate(self.end_date) > relieving_date)
|
(relieving_date and getdate(self.end_date) > relieving_date)
|
||||||
)):
|
)):
|
||||||
@ -952,7 +954,7 @@ class SalarySlip(TransactionBase):
|
|||||||
amount = flt((flt(row.default_amount) * flt(self.payment_days)
|
amount = flt((flt(row.default_amount) * flt(self.payment_days)
|
||||||
/ cint(self.total_working_days)), row.precision("amount")) + additional_amount
|
/ cint(self.total_working_days)), row.precision("amount")) + additional_amount
|
||||||
|
|
||||||
elif not self.payment_days and not self.salary_slip_based_on_timesheet and cint(row.depends_on_payment_days):
|
elif not self.payment_days and row.salary_component != timesheet_component and cint(row.depends_on_payment_days):
|
||||||
amount, additional_amount = 0, 0
|
amount, additional_amount = 0, 0
|
||||||
elif not row.amount:
|
elif not row.amount:
|
||||||
amount = flt(row.default_amount) + flt(row.additional_amount)
|
amount = flt(row.default_amount) + flt(row.additional_amount)
|
||||||
|
@ -134,6 +134,57 @@ class TestSalarySlip(unittest.TestCase):
|
|||||||
|
|
||||||
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
|
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
|
||||||
|
|
||||||
|
def test_payment_days_in_salary_slip_based_on_timesheet(self):
|
||||||
|
from erpnext.hr.doctype.attendance.attendance import mark_attendance
|
||||||
|
from erpnext.projects.doctype.timesheet.test_timesheet import (
|
||||||
|
make_salary_structure_for_timesheet,
|
||||||
|
make_timesheet,
|
||||||
|
)
|
||||||
|
from erpnext.projects.doctype.timesheet.timesheet import (
|
||||||
|
make_salary_slip as make_salary_slip_for_timesheet,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Payroll based on attendance
|
||||||
|
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance")
|
||||||
|
|
||||||
|
emp = make_employee("test_employee_timesheet@salary.com", company="_Test Company")
|
||||||
|
frappe.db.set_value("Employee", emp, {"relieving_date": None, "status": "Active"})
|
||||||
|
|
||||||
|
# mark attendance
|
||||||
|
month_start_date = get_first_day(nowdate())
|
||||||
|
month_end_date = get_last_day(nowdate())
|
||||||
|
|
||||||
|
first_sunday = frappe.db.sql("""
|
||||||
|
select holiday_date from `tabHoliday`
|
||||||
|
where parent = 'Salary Slip Test Holiday List'
|
||||||
|
and holiday_date between %s and %s
|
||||||
|
order by holiday_date
|
||||||
|
""", (month_start_date, month_end_date))[0][0]
|
||||||
|
|
||||||
|
mark_attendance(emp, add_days(first_sunday, 1), 'Absent', ignore_validate=True) # counted as absent
|
||||||
|
|
||||||
|
# salary structure based on timesheet
|
||||||
|
make_salary_structure_for_timesheet(emp)
|
||||||
|
timesheet = make_timesheet(emp, simulate=True, is_billable=1)
|
||||||
|
salary_slip = make_salary_slip_for_timesheet(timesheet.name)
|
||||||
|
salary_slip.start_date = month_start_date
|
||||||
|
salary_slip.end_date = month_end_date
|
||||||
|
salary_slip.save()
|
||||||
|
salary_slip.submit()
|
||||||
|
|
||||||
|
no_of_days = self.get_no_of_days()
|
||||||
|
days_in_month = no_of_days[0]
|
||||||
|
no_of_holidays = no_of_days[1]
|
||||||
|
|
||||||
|
self.assertEqual(salary_slip.payment_days, days_in_month - no_of_holidays - 1)
|
||||||
|
|
||||||
|
# gross pay calculation based on attendance (payment days)
|
||||||
|
gross_pay = 78100 - ((78000 / (days_in_month - no_of_holidays)) * flt(salary_slip.leave_without_pay + salary_slip.absent_days))
|
||||||
|
|
||||||
|
self.assertEqual(salary_slip.gross_pay, flt(gross_pay, 2))
|
||||||
|
|
||||||
|
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
|
||||||
|
|
||||||
def test_component_amount_dependent_on_another_payment_days_based_component(self):
|
def test_component_amount_dependent_on_another_payment_days_based_component(self):
|
||||||
from erpnext.hr.doctype.attendance.attendance import mark_attendance
|
from erpnext.hr.doctype.attendance.attendance import mark_attendance
|
||||||
from erpnext.payroll.doctype.salary_structure.test_salary_structure import (
|
from erpnext.payroll.doctype.salary_structure.test_salary_structure import (
|
||||||
|
@ -34,10 +34,6 @@ class TestTimesheet(unittest.TestCase):
|
|||||||
for dt in ["Salary Slip", "Salary Structure", "Salary Structure Assignment", "Timesheet"]:
|
for dt in ["Salary Slip", "Salary Structure", "Salary Structure Assignment", "Timesheet"]:
|
||||||
frappe.db.sql("delete from `tab%s`" % dt)
|
frappe.db.sql("delete from `tab%s`" % dt)
|
||||||
|
|
||||||
if not frappe.db.exists("Salary Component", "Timesheet Component"):
|
|
||||||
frappe.get_doc({"doctype": "Salary Component", "salary_component": "Timesheet Component"}).insert()
|
|
||||||
|
|
||||||
|
|
||||||
def test_timesheet_billing_amount(self):
|
def test_timesheet_billing_amount(self):
|
||||||
emp = make_employee("test_employee_6@salary.com")
|
emp = make_employee("test_employee_6@salary.com")
|
||||||
|
|
||||||
@ -160,6 +156,9 @@ def make_salary_structure_for_timesheet(employee, company=None):
|
|||||||
salary_structure_name = "Timesheet Salary Structure Test"
|
salary_structure_name = "Timesheet Salary Structure Test"
|
||||||
frequency = "Monthly"
|
frequency = "Monthly"
|
||||||
|
|
||||||
|
if not frappe.db.exists("Salary Component", "Timesheet Component"):
|
||||||
|
frappe.get_doc({"doctype": "Salary Component", "salary_component": "Timesheet Component"}).insert()
|
||||||
|
|
||||||
salary_structure = make_salary_structure(salary_structure_name, frequency, company=company, dont_submit=True)
|
salary_structure = make_salary_structure(salary_structure_name, frequency, company=company, dont_submit=True)
|
||||||
salary_structure.salary_component = "Timesheet Component"
|
salary_structure.salary_component = "Timesheet Component"
|
||||||
salary_structure.salary_slip_based_on_timesheet = 1
|
salary_structure.salary_slip_based_on_timesheet = 1
|
||||||
|
@ -430,12 +430,9 @@ erpnext.utils.select_alternate_items = function(opts) {
|
|||||||
qty = row.qty;
|
qty = row.qty;
|
||||||
}
|
}
|
||||||
row[item_field] = d.alternate_item;
|
row[item_field] = d.alternate_item;
|
||||||
frm.script_manager.trigger(item_field, row.doctype, row.name)
|
frappe.model.set_value(row.doctype, row.name, 'qty', qty);
|
||||||
.then(() => {
|
frappe.model.set_value(row.doctype, row.name, opts.original_item_field, d.item_code);
|
||||||
frappe.model.set_value(row.doctype, row.name, 'qty', qty);
|
frm.trigger(item_field, row.doctype, row.name);
|
||||||
frappe.model.set_value(row.doctype, row.name,
|
|
||||||
opts.original_item_field, d.item_code);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
refresh_field(opts.child_docname);
|
refresh_field(opts.child_docname);
|
||||||
@ -888,9 +885,11 @@ $(document).on('app_ready', function() {
|
|||||||
function set_time_to_resolve_and_response(frm, apply_sla_for_resolution) {
|
function set_time_to_resolve_and_response(frm, apply_sla_for_resolution) {
|
||||||
frm.dashboard.clear_headline();
|
frm.dashboard.clear_headline();
|
||||||
|
|
||||||
let time_to_respond = get_status(frm.doc.response_by);
|
let time_to_respond;
|
||||||
if (!frm.doc.first_responded_on) {
|
if (!frm.doc.first_responded_on) {
|
||||||
time_to_respond = get_time_left(frm.doc.response_by, frm.doc.agreement_status);
|
time_to_respond = get_time_left(frm.doc.response_by, frm.doc.agreement_status);
|
||||||
|
} else {
|
||||||
|
time_to_respond = get_status(frm.doc.response_by, frm.doc.first_responded_on);
|
||||||
}
|
}
|
||||||
|
|
||||||
let alert = `
|
let alert = `
|
||||||
@ -903,9 +902,11 @@ function set_time_to_resolve_and_response(frm, apply_sla_for_resolution) {
|
|||||||
|
|
||||||
|
|
||||||
if (apply_sla_for_resolution) {
|
if (apply_sla_for_resolution) {
|
||||||
let time_to_resolve = get_status(frm.doc.resolution_by);
|
let time_to_resolve;
|
||||||
if (!frm.doc.resolution_date) {
|
if (!frm.doc.resolution_date) {
|
||||||
time_to_resolve = get_time_left(frm.doc.resolution_by, frm.doc.agreement_status);
|
time_to_resolve = get_time_left(frm.doc.resolution_by, frm.doc.agreement_status);
|
||||||
|
} else {
|
||||||
|
time_to_resolve = get_status(frm.doc.resolution_by, frm.doc.resolution_date);
|
||||||
}
|
}
|
||||||
|
|
||||||
alert += `
|
alert += `
|
||||||
@ -928,8 +929,8 @@ function get_time_left(timestamp, agreement_status) {
|
|||||||
return {'diff_display': diff_display, 'indicator': indicator};
|
return {'diff_display': diff_display, 'indicator': indicator};
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_status(timestamp) {
|
function get_status(expected, actual) {
|
||||||
const time_left = moment(timestamp).diff(moment());
|
const time_left = moment(expected).diff(moment(actual));
|
||||||
if (time_left >= 0) {
|
if (time_left >= 0) {
|
||||||
return {'diff_display': 'Fulfilled', 'indicator': 'green'};
|
return {'diff_display': 'Fulfilled', 'indicator': 'green'};
|
||||||
} else {
|
} else {
|
||||||
|
@ -114,9 +114,11 @@ def get_items(filters):
|
|||||||
|
|
||||||
items = frappe.db.sql("""
|
items = frappe.db.sql("""
|
||||||
select
|
select
|
||||||
`tabSales Invoice Item`.name, `tabSales Invoice Item`.base_price_list_rate,
|
`tabSales Invoice Item`.gst_hsn_code,
|
||||||
`tabSales Invoice Item`.gst_hsn_code, `tabSales Invoice Item`.stock_qty,
|
`tabSales Invoice Item`.stock_uom,
|
||||||
`tabSales Invoice Item`.stock_uom, `tabSales Invoice Item`.base_net_amount,
|
sum(`tabSales Invoice Item`.stock_qty) as stock_qty,
|
||||||
|
sum(`tabSales Invoice Item`.base_net_amount) as base_net_amount,
|
||||||
|
sum(`tabSales Invoice Item`.base_price_list_rate) as base_price_list_rate,
|
||||||
`tabSales Invoice Item`.parent, `tabSales Invoice Item`.item_code,
|
`tabSales Invoice Item`.parent, `tabSales Invoice Item`.item_code,
|
||||||
`tabGST HSN Code`.description
|
`tabGST HSN Code`.description
|
||||||
from `tabSales Invoice`, `tabSales Invoice Item`, `tabGST HSN Code`
|
from `tabSales Invoice`, `tabSales Invoice Item`, `tabGST HSN Code`
|
||||||
@ -124,6 +126,8 @@ def get_items(filters):
|
|||||||
and `tabSales Invoice`.docstatus = 1
|
and `tabSales Invoice`.docstatus = 1
|
||||||
and `tabSales Invoice Item`.gst_hsn_code is not NULL
|
and `tabSales Invoice Item`.gst_hsn_code is not NULL
|
||||||
and `tabSales Invoice Item`.gst_hsn_code = `tabGST HSN Code`.name %s %s
|
and `tabSales Invoice Item`.gst_hsn_code = `tabGST HSN Code`.name %s %s
|
||||||
|
group by
|
||||||
|
`tabSales Invoice Item`.parent, `tabSales Invoice Item`.item_code
|
||||||
|
|
||||||
""" % (conditions, match_conditions), filters, as_dict=1)
|
""" % (conditions, match_conditions), filters, as_dict=1)
|
||||||
|
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
from erpnext.regional.doctype.gstr_3b_report.test_gstr_3b_report import (
|
||||||
|
make_company as setup_company,
|
||||||
|
)
|
||||||
|
from erpnext.regional.doctype.gstr_3b_report.test_gstr_3b_report import (
|
||||||
|
make_customers as setup_customers,
|
||||||
|
)
|
||||||
|
from erpnext.regional.doctype.gstr_3b_report.test_gstr_3b_report import (
|
||||||
|
set_account_heads as setup_gst_settings,
|
||||||
|
)
|
||||||
|
from erpnext.regional.report.hsn_wise_summary_of_outward_supplies.hsn_wise_summary_of_outward_supplies import (
|
||||||
|
execute as run_report,
|
||||||
|
)
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
|
||||||
|
|
||||||
|
class TestHSNWiseSummaryReport(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
setup_company()
|
||||||
|
setup_customers()
|
||||||
|
setup_gst_settings()
|
||||||
|
make_item("Golf Car", properties={ "gst_hsn_code": "999900" })
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
frappe.db.rollback()
|
||||||
|
|
||||||
|
def test_hsn_summary_for_invoice_with_duplicate_items(self):
|
||||||
|
si = create_sales_invoice(
|
||||||
|
company="_Test Company GST",
|
||||||
|
customer = "_Test GST Customer",
|
||||||
|
currency = "INR",
|
||||||
|
warehouse = "Finished Goods - _GST",
|
||||||
|
debit_to = "Debtors - _GST",
|
||||||
|
income_account = "Sales - _GST",
|
||||||
|
expense_account = "Cost of Goods Sold - _GST",
|
||||||
|
cost_center = "Main - _GST",
|
||||||
|
do_not_save=1
|
||||||
|
)
|
||||||
|
|
||||||
|
si.items = []
|
||||||
|
si.append("items", {
|
||||||
|
"item_code": "Golf Car",
|
||||||
|
"gst_hsn_code": "999900",
|
||||||
|
"qty": "1",
|
||||||
|
"rate": "120",
|
||||||
|
"cost_center": "Main - _GST"
|
||||||
|
})
|
||||||
|
si.append("items", {
|
||||||
|
"item_code": "Golf Car",
|
||||||
|
"gst_hsn_code": "999900",
|
||||||
|
"qty": "1",
|
||||||
|
"rate": "140",
|
||||||
|
"cost_center": "Main - _GST"
|
||||||
|
})
|
||||||
|
si.append("taxes", {
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "Output Tax IGST - _GST",
|
||||||
|
"cost_center": "Main - _GST",
|
||||||
|
"description": "IGST @ 18.0",
|
||||||
|
"rate": 18
|
||||||
|
})
|
||||||
|
si.posting_date = "2020-11-17"
|
||||||
|
si.submit()
|
||||||
|
si.reload()
|
||||||
|
|
||||||
|
[columns, data] = run_report(filters=frappe._dict({
|
||||||
|
"company": "_Test Company GST",
|
||||||
|
"gst_hsn_code": "999900",
|
||||||
|
"company_gstin": si.company_gstin,
|
||||||
|
"from_date": si.posting_date,
|
||||||
|
"to_date": si.posting_date
|
||||||
|
}))
|
||||||
|
|
||||||
|
filtered_rows = list(filter(lambda row: row['gst_hsn_code'] == "999900", data))
|
||||||
|
self.assertTrue(filtered_rows)
|
||||||
|
|
||||||
|
hsn_row = filtered_rows[0]
|
||||||
|
self.assertEquals(hsn_row['stock_qty'], 2.0)
|
||||||
|
self.assertEquals(hsn_row['total_amount'], 306.8)
|
@ -2,8 +2,6 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
|
||||||
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 flt
|
from frappe.utils import flt
|
||||||
@ -11,7 +9,7 @@ from frappe.utils import flt
|
|||||||
from erpnext.accounts.party import get_due_date
|
from erpnext.accounts.party import get_due_date
|
||||||
from erpnext.exceptions import PartyDisabled, PartyFrozen
|
from erpnext.exceptions import PartyDisabled, PartyFrozen
|
||||||
from erpnext.selling.doctype.customer.customer import get_credit_limit, get_customer_outstanding
|
from erpnext.selling.doctype.customer.customer import get_credit_limit, get_customer_outstanding
|
||||||
from erpnext.tests.utils import create_test_contact_and_address
|
from erpnext.tests.utils import ERPNextTestCase, create_test_contact_and_address
|
||||||
|
|
||||||
test_ignore = ["Price List"]
|
test_ignore = ["Price List"]
|
||||||
test_dependencies = ['Payment Term', 'Payment Terms Template']
|
test_dependencies = ['Payment Term', 'Payment Terms Template']
|
||||||
@ -19,7 +17,7 @@ test_records = frappe.get_test_records('Customer')
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TestCustomer(unittest.TestCase):
|
class TestCustomer(ERPNextTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
if not frappe.get_value('Item', '_Test Item'):
|
if not frappe.get_value('Item', '_Test Item'):
|
||||||
make_test_records('Item')
|
make_test_records('Item')
|
||||||
|
@ -6,6 +6,7 @@ import unittest
|
|||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
from erpnext.controllers.queries import item_query
|
from erpnext.controllers.queries import item_query
|
||||||
|
from erpnext.tests.utils import ERPNextTestCase
|
||||||
|
|
||||||
test_dependencies = ['Item', 'Customer', 'Supplier']
|
test_dependencies = ['Item', 'Customer', 'Supplier']
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ def create_party_specific_item(**args):
|
|||||||
psi.based_on_value = args.get('based_on_value')
|
psi.based_on_value = args.get('based_on_value')
|
||||||
psi.insert()
|
psi.insert()
|
||||||
|
|
||||||
class TestPartySpecificItem(unittest.TestCase):
|
class TestPartySpecificItem(ERPNextTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.customer = frappe.get_last_doc("Customer")
|
self.customer = frappe.get_last_doc("Customer")
|
||||||
self.supplier = frappe.get_last_doc("Supplier")
|
self.supplier = frappe.get_last_doc("Supplier")
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import add_days, add_months, flt, getdate, nowdate
|
from frappe.utils import add_days, add_months, flt, getdate, nowdate
|
||||||
|
|
||||||
|
from erpnext.tests.utils import ERPNextTestCase
|
||||||
|
|
||||||
test_dependencies = ["Product Bundle"]
|
test_dependencies = ["Product Bundle"]
|
||||||
|
|
||||||
|
|
||||||
class TestQuotation(unittest.TestCase):
|
class TestQuotation(ERPNextTestCase):
|
||||||
def test_make_quotation_without_terms(self):
|
def test_make_quotation_without_terms(self):
|
||||||
quotation = make_quotation(do_not_save=1)
|
quotation = make_quotation(do_not_save=1)
|
||||||
self.assertFalse(quotation.get('payment_schedule'))
|
self.assertFalse(quotation.get('payment_schedule'))
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import unittest
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import frappe.permissions
|
import frappe.permissions
|
||||||
@ -28,12 +27,14 @@ from erpnext.selling.doctype.sales_order.sales_order import (
|
|||||||
)
|
)
|
||||||
from erpnext.stock.doctype.item.test_item import make_item
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
|
from erpnext.tests.utils import ERPNextTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestSalesOrder(unittest.TestCase):
|
class TestSalesOrder(ERPNextTestCase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
cls.unlink_setting = int(frappe.db.get_value("Accounts Settings", "Accounts Settings",
|
cls.unlink_setting = int(frappe.db.get_value("Accounts Settings", "Accounts Settings",
|
||||||
"unlink_advance_payment_on_cancelation_of_order"))
|
"unlink_advance_payment_on_cancelation_of_order"))
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ class TestSalesOrder(unittest.TestCase):
|
|||||||
# reset config to previous state
|
# reset config to previous state
|
||||||
frappe.db.set_value("Accounts Settings", "Accounts Settings",
|
frappe.db.set_value("Accounts Settings", "Accounts Settings",
|
||||||
"unlink_advance_payment_on_cancelation_of_order", cls.unlink_setting)
|
"unlink_advance_payment_on_cancelation_of_order", cls.unlink_setting)
|
||||||
|
super().tearDownClass()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
|
29
erpnext/selling/form_tour/customer/customer.json
Normal file
29
erpnext/selling/form_tour/customer/customer.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"creation": "2021-11-23 10:44:13.185982",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Form Tour",
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": 1,
|
||||||
|
"modified": "2021-11-23 10:54:09.602358",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Selling",
|
||||||
|
"name": "Customer",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"reference_doctype": "Customer",
|
||||||
|
"save_on_complete": 1,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"description": "Enter the Full Name of the Customer",
|
||||||
|
"field": "",
|
||||||
|
"fieldname": "customer_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"has_next_condition": 0,
|
||||||
|
"is_table_field": 0,
|
||||||
|
"label": "Full Name",
|
||||||
|
"parent_field": "",
|
||||||
|
"position": "Left",
|
||||||
|
"title": "Full Name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Customer"
|
||||||
|
}
|
67
erpnext/selling/form_tour/quotation/quotation.json
Normal file
67
erpnext/selling/form_tour/quotation/quotation.json
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"creation": "2021-11-23 12:00:36.138824",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Form Tour",
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": 1,
|
||||||
|
"modified": "2021-11-23 12:02:48.010298",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Selling",
|
||||||
|
"name": "Quotation",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"reference_doctype": "Quotation",
|
||||||
|
"save_on_complete": 1,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"description": "Select a customer or lead for whom this quotation is being prepared. Let's select a Customer.",
|
||||||
|
"field": "",
|
||||||
|
"fieldname": "quotation_to",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"has_next_condition": 0,
|
||||||
|
"is_table_field": 0,
|
||||||
|
"label": "Quotation To",
|
||||||
|
"parent_field": "",
|
||||||
|
"position": "Right",
|
||||||
|
"title": "Quotation To"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Select a specific Customer to whom this quotation will be sent.",
|
||||||
|
"field": "",
|
||||||
|
"fieldname": "party_name",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"has_next_condition": 0,
|
||||||
|
"is_table_field": 0,
|
||||||
|
"label": "Party",
|
||||||
|
"parent_field": "",
|
||||||
|
"position": "Right",
|
||||||
|
"title": "Party"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child_doctype": "Quotation Item",
|
||||||
|
"description": "Select an item for which you will be quoting a price.",
|
||||||
|
"field": "",
|
||||||
|
"fieldname": "items",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"has_next_condition": 0,
|
||||||
|
"is_table_field": 0,
|
||||||
|
"label": "Items",
|
||||||
|
"parent_field": "",
|
||||||
|
"parent_fieldname": "items",
|
||||||
|
"position": "Bottom",
|
||||||
|
"title": "Items"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "You can select pre-populated Sales Taxes and Charges from here.",
|
||||||
|
"field": "",
|
||||||
|
"fieldname": "taxes",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"has_next_condition": 0,
|
||||||
|
"is_table_field": 0,
|
||||||
|
"label": "Sales Taxes and Charges",
|
||||||
|
"parent_field": "",
|
||||||
|
"position": "Bottom",
|
||||||
|
"title": "Sales Taxes and Charges"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Quotation"
|
||||||
|
}
|
@ -2,8 +2,6 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from frappe.utils import add_months, nowdate
|
from frappe.utils import add_months, nowdate
|
||||||
|
|
||||||
from erpnext.selling.doctype.sales_order.sales_order import make_material_request
|
from erpnext.selling.doctype.sales_order.sales_order import make_material_request
|
||||||
@ -11,9 +9,10 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde
|
|||||||
from erpnext.selling.report.pending_so_items_for_purchase_request.pending_so_items_for_purchase_request import (
|
from erpnext.selling.report.pending_so_items_for_purchase_request.pending_so_items_for_purchase_request import (
|
||||||
execute,
|
execute,
|
||||||
)
|
)
|
||||||
|
from erpnext.tests.utils import ERPNextTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestPendingSOItemsForPurchaseRequest(unittest.TestCase):
|
class TestPendingSOItemsForPurchaseRequest(ERPNextTestCase):
|
||||||
def test_result_for_partial_material_request(self):
|
def test_result_for_partial_material_request(self):
|
||||||
so = make_sales_order()
|
so = make_sales_order()
|
||||||
mr=make_material_request(so.name)
|
mr=make_material_request(so.name)
|
||||||
|
@ -2,15 +2,14 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
from erpnext.selling.report.sales_analytics.sales_analytics import execute
|
from erpnext.selling.report.sales_analytics.sales_analytics import execute
|
||||||
|
from erpnext.tests.utils import ERPNextTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestAnalytics(unittest.TestCase):
|
class TestAnalytics(ERPNextTestCase):
|
||||||
def test_sales_analytics(self):
|
def test_sales_analytics(self):
|
||||||
frappe.db.sql("delete from `tabSales Order` where company='_Test Company 2'")
|
frappe.db.sql("delete from `tabSales Order` where company='_Test Company 2'")
|
||||||
|
|
||||||
|
67
erpnext/setup/form_tour/company/company.json
Normal file
67
erpnext/setup/form_tour/company/company.json
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"creation": "2021-11-24 10:17:18.534917",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Form Tour",
|
||||||
|
"first_document": 1,
|
||||||
|
"idx": 0,
|
||||||
|
"include_name_field": 0,
|
||||||
|
"is_standard": 1,
|
||||||
|
"modified": "2021-11-24 15:38:21.026582",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Setup",
|
||||||
|
"name": "Company",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"reference_doctype": "Company",
|
||||||
|
"save_on_complete": 0,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"description": "This is the default currency for this company.",
|
||||||
|
"field": "",
|
||||||
|
"fieldname": "default_currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"has_next_condition": 0,
|
||||||
|
"is_table_field": 0,
|
||||||
|
"label": "Default Currency",
|
||||||
|
"parent_field": "",
|
||||||
|
"position": "Right",
|
||||||
|
"title": "Default Currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Here, you can add multiple addresses of the company",
|
||||||
|
"field": "",
|
||||||
|
"fieldname": "company_info",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"has_next_condition": 0,
|
||||||
|
"is_table_field": 0,
|
||||||
|
"label": "Address & Contact",
|
||||||
|
"parent_field": "",
|
||||||
|
"position": "Top",
|
||||||
|
"title": "Address & Contact"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Here, you can set default Accounts, which will ease the creation of accounting entries.",
|
||||||
|
"field": "",
|
||||||
|
"fieldname": "default_settings",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"has_next_condition": 0,
|
||||||
|
"is_table_field": 0,
|
||||||
|
"label": "Accounts Settings",
|
||||||
|
"parent_field": "",
|
||||||
|
"position": "Top",
|
||||||
|
"title": "Accounts Settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "This setting is recommended if you wish to track the real-time stock balance in your books of account. This will allow the creation of a General Ledger entry for every stock transaction.",
|
||||||
|
"field": "",
|
||||||
|
"fieldname": "enable_perpetual_inventory",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"has_next_condition": 0,
|
||||||
|
"is_table_field": 0,
|
||||||
|
"label": "Enable Perpetual Inventory",
|
||||||
|
"parent_field": "",
|
||||||
|
"position": "Right",
|
||||||
|
"title": "Enable Perpetual Inventory"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Company"
|
||||||
|
}
|
62
erpnext/setup/module_onboarding/home/home.json
Normal file
62
erpnext/setup/module_onboarding/home/home.json
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"allow_roles": [
|
||||||
|
{
|
||||||
|
"role": "Accounts Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Stock Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Sales Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Purchase Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Manufacturing Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Item Manager"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"creation": "2021-11-22 12:19:15.888642",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Module Onboarding",
|
||||||
|
"documentation_url": "https://docs.erpnext.com/docs/v13/user/manual/en/setting-up/company-setup",
|
||||||
|
"idx": 0,
|
||||||
|
"is_complete": 0,
|
||||||
|
"modified": "2021-12-15 14:23:52.460913",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Setup",
|
||||||
|
"name": "Home",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step": "Company Set Up"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "Navigation Help"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "Data import"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "Create an Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "Create a Customer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "Create a Supplier"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "Create a Quotation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "Letterhead"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subtitle": "Company, Item, Customer, Supplier, Navigation Help, Data Import, Letter Head, Quotation",
|
||||||
|
"success_message": "Masters are all set up!",
|
||||||
|
"title": "Let's Set Up Some Masters"
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"action": "Create Entry",
|
||||||
|
"action_label": "Let's review your Company",
|
||||||
|
"creation": "2021-11-22 11:55:48.931427",
|
||||||
|
"description": "# Set Up a Company\n\nA company is a legal entity for which you will set up your books of account and create accounting transactions. In ERPNext, you can create multiple companies, and establish relationships (group/subsidiary) among them.\n\nWithin the company master, you can capture various default accounts for that Company and set crucial settings related to the accounting methodology followed for a company.\n",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Onboarding Step",
|
||||||
|
"idx": 0,
|
||||||
|
"is_complete": 0,
|
||||||
|
"is_single": 0,
|
||||||
|
"is_skipped": 0,
|
||||||
|
"modified": "2021-12-15 14:22:18.317423",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"name": "Company Set Up",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"reference_document": "Company",
|
||||||
|
"show_form_tour": 1,
|
||||||
|
"show_full_form": 1,
|
||||||
|
"title": "Set Up a Company",
|
||||||
|
"validate_action": 1
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"action": "Create Entry",
|
||||||
|
"action_label": "Let\u2019s create your first Customer",
|
||||||
|
"creation": "2020-05-14 17:46:41.831517",
|
||||||
|
"description": "# Create a Customer\n\nThe Customer master is at the heart of your sales transactions. Customers are linked in Quotations, Sales Orders, Invoices, and Payments. Customers can be either numbered or identified by name (you would typically do this based on the number of customers you have).\n\nThrough Customer\u2019s master, you can effectively track essentials like:\n - Customer\u2019s multiple address and contacts\n - Account Receivables\n - Credit Limit and Credit Period\n",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Onboarding Step",
|
||||||
|
"idx": 0,
|
||||||
|
"is_complete": 0,
|
||||||
|
"is_single": 0,
|
||||||
|
"is_skipped": 0,
|
||||||
|
"modified": "2021-12-15 14:20:31.197564",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"name": "Create a Customer",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"reference_document": "Customer",
|
||||||
|
"show_form_tour": 0,
|
||||||
|
"show_full_form": 0,
|
||||||
|
"title": "Manage Customers",
|
||||||
|
"validate_action": 1
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"action": "Create Entry",
|
||||||
|
"action_label": "Let\u2019s create your first Quotation",
|
||||||
|
"creation": "2020-06-01 13:34:58.958641",
|
||||||
|
"description": "# Create a Quotation\n\nLet\u2019s get started with business transactions by creating your first Quotation. You can create a Quotation for an existing customer or a prospect. It will be an approved document, with items you sell and the proposed price + taxes applied. After completing the instructions, you will get a Quotation in a ready to share print format.",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Onboarding Step",
|
||||||
|
"idx": 0,
|
||||||
|
"is_complete": 0,
|
||||||
|
"is_single": 0,
|
||||||
|
"is_skipped": 0,
|
||||||
|
"modified": "2021-12-15 14:21:31.675330",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"name": "Create a Quotation",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"reference_document": "Quotation",
|
||||||
|
"show_form_tour": 1,
|
||||||
|
"show_full_form": 1,
|
||||||
|
"title": "Create your first Quotation",
|
||||||
|
"validate_action": 1
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"action": "Create Entry",
|
||||||
|
"action_label": "Let\u2019s create your first Supplier",
|
||||||
|
"creation": "2020-05-14 22:09:10.043554",
|
||||||
|
"description": "# Create a Supplier\n\nAlso known as Vendor, is a master at the center of your purchase transactions. Suppliers are linked in Request for Quotation, Purchase Orders, Receipts, and Payments. Suppliers can be either numbered or identified by name.\n\nThrough Supplier\u2019s master, you can effectively track essentials like:\n - Supplier\u2019s multiple address and contacts\n - Account Receivables\n - Credit Limit and Credit Period\n",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Onboarding Step",
|
||||||
|
"idx": 0,
|
||||||
|
"is_complete": 0,
|
||||||
|
"is_single": 0,
|
||||||
|
"is_skipped": 0,
|
||||||
|
"modified": "2021-12-15 14:21:23.518301",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"name": "Create a Supplier",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"reference_document": "Supplier",
|
||||||
|
"show_form_tour": 0,
|
||||||
|
"show_full_form": 0,
|
||||||
|
"title": "Manage Suppliers",
|
||||||
|
"validate_action": 1
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"action": "Create Entry",
|
||||||
|
"action_label": "Create a new Item",
|
||||||
|
"creation": "2021-05-17 13:47:18.515052",
|
||||||
|
"description": "# Create an Item\n\nItem is a product, of a or service offered by your company, or something you buy as a part of your supplies or raw materials.\n\nItems are integral to everything you do in ERPNext - from billing, purchasing to managing inventory. Everything you buy or sell, whether it is a physical product or a service is an Item. Items can be stock, non-stock, variants, serialized, batched, assets etc.\n",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Onboarding Step",
|
||||||
|
"form_tour": "Item General",
|
||||||
|
"idx": 0,
|
||||||
|
"intro_video_url": "",
|
||||||
|
"is_complete": 0,
|
||||||
|
"is_single": 0,
|
||||||
|
"is_skipped": 0,
|
||||||
|
"modified": "2021-12-15 14:19:56.297772",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"name": "Create an Item",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"reference_document": "Item",
|
||||||
|
"show_form_tour": 1,
|
||||||
|
"show_full_form": 1,
|
||||||
|
"title": "Manage Items",
|
||||||
|
"validate_action": 1
|
||||||
|
}
|
21
erpnext/setup/onboarding_step/data_import/data_import.json
Normal file
21
erpnext/setup/onboarding_step/data_import/data_import.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"action": "Watch Video",
|
||||||
|
"action_label": "Learn more about data migration",
|
||||||
|
"creation": "2021-05-19 05:29:16.809610",
|
||||||
|
"description": "# Import Data from Spreadsheet\n\nIn ERPNext, you can easily migrate your historical data using spreadsheets. You can use it for migrating not just masters (like Customer, Supplier, Items), but also for transactions like (outstanding invoices, opening stock and accounting entries, etc). If you are migrating from [Tally](https://tallysolutions.com/) or [Quickbooks](https://quickbooks.intuit.com/in/), we got special migration tools for you.",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Onboarding Step",
|
||||||
|
"idx": 0,
|
||||||
|
"is_complete": 0,
|
||||||
|
"is_single": 0,
|
||||||
|
"is_skipped": 0,
|
||||||
|
"modified": "2021-12-15 13:10:57.346422",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"name": "Data import",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"show_form_tour": 0,
|
||||||
|
"show_full_form": 0,
|
||||||
|
"title": "Import Data from Spreadsheet",
|
||||||
|
"validate_action": 1,
|
||||||
|
"video_url": "https://youtu.be/DQyqeurPI64"
|
||||||
|
}
|
21
erpnext/setup/onboarding_step/letterhead/letterhead.json
Normal file
21
erpnext/setup/onboarding_step/letterhead/letterhead.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"action": "Create Entry",
|
||||||
|
"action_label": "Let\u2019s setup your first Letter Head",
|
||||||
|
"creation": "2021-11-22 12:36:34.583783",
|
||||||
|
"description": "# Create a Letter Head\n\nA Letter Head contains your organization's name, logo, address, etc which appears at the header and footer portion in documents. You can learn more about Setting up Letter Head in ERPNext here.\n",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Onboarding Step",
|
||||||
|
"idx": 0,
|
||||||
|
"is_complete": 0,
|
||||||
|
"is_single": 0,
|
||||||
|
"is_skipped": 0,
|
||||||
|
"modified": "2021-12-15 14:21:39.037742",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"name": "Letterhead",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"reference_document": "Letter Head",
|
||||||
|
"show_form_tour": 1,
|
||||||
|
"show_full_form": 1,
|
||||||
|
"title": "Setup Your Letterhead",
|
||||||
|
"validate_action": 1
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"action": "Watch Video",
|
||||||
|
"action_label": "Learn about Navigation options",
|
||||||
|
"creation": "2021-11-22 12:09:52.233872",
|
||||||
|
"description": "# Navigation in ERPNext\n\nEase of navigating and browsing around the ERPNext is one of our core strengths. In the following video, you will learn how to reach a specific feature in ERPNext via module page or awesome bar\u2019s shortcut.\n",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Onboarding Step",
|
||||||
|
"idx": 0,
|
||||||
|
"is_complete": 0,
|
||||||
|
"is_single": 0,
|
||||||
|
"is_skipped": 0,
|
||||||
|
"modified": "2021-12-15 14:20:55.441678",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"name": "Navigation Help",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"show_form_tour": 0,
|
||||||
|
"show_full_form": 0,
|
||||||
|
"title": "How to Navigate in ERPNext",
|
||||||
|
"validate_action": 1,
|
||||||
|
"video_url": "https://youtu.be/j60xyNFqX_A"
|
||||||
|
}
|
@ -1,13 +1,18 @@
|
|||||||
{
|
{
|
||||||
"charts": [],
|
"charts": [],
|
||||||
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Supplier\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leaderboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Human Resources\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"CRM\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data Import and Settings\",\"col\":4}}]",
|
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Home\",\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Supplier\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leaderboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Human Resources\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"CRM\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data Import and Settings\",\"col\":4}}]",
|
||||||
"creation": "2020-01-23 13:46:38.833076",
|
"creation": "2020-01-23 13:46:38.833076",
|
||||||
|
"developer_mode_only": 0,
|
||||||
|
"disable_user_customization": 0,
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Workspace",
|
"doctype": "Workspace",
|
||||||
|
"extends_another_page": 0,
|
||||||
"for_user": "",
|
"for_user": "",
|
||||||
"hide_custom": 0,
|
"hide_custom": 0,
|
||||||
"icon": "getting-started",
|
"icon": "getting-started",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
|
"is_default": 0,
|
||||||
|
"is_standard": 0,
|
||||||
"label": "Home",
|
"label": "Home",
|
||||||
"links": [
|
"links": [
|
||||||
{
|
{
|
||||||
@ -271,12 +276,14 @@
|
|||||||
"type": "Link"
|
"type": "Link"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2021-08-10 15:33:20.704741",
|
"modified": "2021-11-22 12:50:15.771366",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Home",
|
"name": "Home",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"parent_page": "",
|
"parent_page": "",
|
||||||
|
"pin_to_bottom": 0,
|
||||||
|
"pin_to_top": 0,
|
||||||
"public": 1,
|
"public": 1,
|
||||||
"restrict_to_domain": "",
|
"restrict_to_domain": "",
|
||||||
"roles": [],
|
"roles": [],
|
||||||
@ -309,4 +316,4 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "Home"
|
"title": "Home"
|
||||||
}
|
}
|
||||||
|
@ -43,9 +43,9 @@ class Bin(Document):
|
|||||||
frappe.qb
|
frappe.qb
|
||||||
.from_(wo)
|
.from_(wo)
|
||||||
.from_(wo_item)
|
.from_(wo_item)
|
||||||
.select(Case()
|
.select(Sum(Case()
|
||||||
.when(wo.skip_transfer == 0, Sum(wo_item.required_qty - wo_item.transferred_qty))
|
.when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty)
|
||||||
.else_(Sum(wo_item.required_qty - wo_item.consumed_qty))
|
.else_(wo_item.required_qty - wo_item.consumed_qty))
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
(wo_item.item_code == self.item_code)
|
(wo_item.item_code == self.item_code)
|
||||||
|
@ -361,8 +361,7 @@
|
|||||||
"fieldname": "valuation_method",
|
"fieldname": "valuation_method",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Valuation Method",
|
"label": "Valuation Method",
|
||||||
"options": "\nFIFO\nMoving Average",
|
"options": "\nFIFO\nMoving Average"
|
||||||
"set_only_once": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "is_stock_item",
|
"depends_on": "is_stock_item",
|
||||||
@ -1035,7 +1034,7 @@
|
|||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-12-03 08:32:03.869294",
|
"modified": "2021-12-14 04:13:16.857534",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Item",
|
"name": "Item",
|
||||||
|
@ -1,451 +1,140 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_import": 0,
|
"autoname": "hash",
|
||||||
"allow_rename": 0,
|
"creation": "2013-04-08 13:10:16",
|
||||||
"autoname": "hash",
|
"doctype": "DocType",
|
||||||
"beta": 0,
|
"document_type": "Document",
|
||||||
"creation": "2013-04-08 13:10:16",
|
"editable_grid": 1,
|
||||||
"custom": 0,
|
"engine": "InnoDB",
|
||||||
"docstatus": 0,
|
"field_order": [
|
||||||
"doctype": "DocType",
|
"item_code",
|
||||||
"document_type": "Document",
|
"column_break_2",
|
||||||
"editable_grid": 1,
|
"item_name",
|
||||||
"engine": "InnoDB",
|
"batch_no",
|
||||||
|
"desc_section",
|
||||||
|
"description",
|
||||||
|
"quantity_section",
|
||||||
|
"qty",
|
||||||
|
"net_weight",
|
||||||
|
"column_break_10",
|
||||||
|
"stock_uom",
|
||||||
|
"weight_uom",
|
||||||
|
"page_break",
|
||||||
|
"dn_detail"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"fieldname": "item_code",
|
||||||
"bold": 0,
|
"fieldtype": "Link",
|
||||||
"collapsible": 0,
|
"in_global_search": 1,
|
||||||
"columns": 0,
|
"in_list_view": 1,
|
||||||
"fieldname": "item_code",
|
"label": "Item Code",
|
||||||
"fieldtype": "Link",
|
"options": "Item",
|
||||||
"hidden": 0,
|
"print_width": "100px",
|
||||||
"ignore_user_permissions": 0,
|
"reqd": 1,
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 1,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Item Code",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Item",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "100px",
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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_global_search": 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,
|
"fetch_from": "item_code.item_name",
|
||||||
"bold": 0,
|
"fieldname": "item_name",
|
||||||
"collapsible": 0,
|
"fieldtype": "Data",
|
||||||
"columns": 0,
|
"in_list_view": 1,
|
||||||
"fieldname": "item_name",
|
"label": "Item Name",
|
||||||
"fieldtype": "Data",
|
"print_width": "200px",
|
||||||
"hidden": 0,
|
"read_only": 1,
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Item Name",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "item_code.item_name",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "200px",
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "200px"
|
"width": "200px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"fieldname": "batch_no",
|
||||||
"bold": 0,
|
"fieldtype": "Link",
|
||||||
"collapsible": 0,
|
"label": "Batch No",
|
||||||
"columns": 0,
|
"options": "Batch"
|
||||||
"fieldname": "batch_no",
|
},
|
||||||
"fieldtype": "Link",
|
|
||||||
"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": "Batch No",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Batch",
|
|
||||||
"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,
|
"collapsible": 1,
|
||||||
"bold": 0,
|
"fieldname": "desc_section",
|
||||||
"collapsible": 1,
|
"fieldtype": "Section Break",
|
||||||
"columns": 0,
|
"label": "Description"
|
||||||
"fieldname": "desc_section",
|
},
|
||||||
"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,
|
|
||||||
"label": "Description",
|
|
||||||
"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": "description",
|
||||||
"bold": 0,
|
"fieldtype": "Text Editor",
|
||||||
"collapsible": 0,
|
"label": "Description"
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "description",
|
|
||||||
"fieldtype": "Text Editor",
|
|
||||||
"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": "Description",
|
|
||||||
"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": "quantity_section",
|
||||||
"bold": 0,
|
"fieldtype": "Section Break",
|
||||||
"collapsible": 0,
|
"label": "Quantity"
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "quantity_section",
|
|
||||||
"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,
|
|
||||||
"label": "Quantity",
|
|
||||||
"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": "qty",
|
||||||
"bold": 0,
|
"fieldtype": "Float",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "Quantity",
|
||||||
"fieldname": "qty",
|
"print_width": "100px",
|
||||||
"fieldtype": "Float",
|
"reqd": 1,
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Quantity",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "100px",
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"fieldname": "net_weight",
|
||||||
"bold": 0,
|
"fieldtype": "Float",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "Net Weight",
|
||||||
"fieldname": "net_weight",
|
"print_width": "100px",
|
||||||
"fieldtype": "Float",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Net Weight",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "100px",
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"fieldname": "column_break_10",
|
||||||
"bold": 0,
|
"fieldtype": "Column Break"
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_10",
|
|
||||||
"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,
|
|
||||||
"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": "stock_uom",
|
||||||
"bold": 0,
|
"fieldtype": "Link",
|
||||||
"collapsible": 0,
|
"label": "UOM",
|
||||||
"columns": 0,
|
"options": "UOM",
|
||||||
"fieldname": "stock_uom",
|
"print_width": "100px",
|
||||||
"fieldtype": "Link",
|
"read_only": 1,
|
||||||
"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": "UOM",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "UOM",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "100px",
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"fieldname": "weight_uom",
|
||||||
"bold": 0,
|
"fieldtype": "Link",
|
||||||
"collapsible": 0,
|
"label": "Weight UOM",
|
||||||
"columns": 0,
|
"options": "UOM",
|
||||||
"fieldname": "weight_uom",
|
"print_width": "100px",
|
||||||
"fieldtype": "Link",
|
|
||||||
"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": "Weight UOM",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "UOM",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "100px",
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"bold": 0,
|
"default": "0",
|
||||||
"collapsible": 0,
|
"fieldname": "page_break",
|
||||||
"columns": 0,
|
"fieldtype": "Check",
|
||||||
"fieldname": "page_break",
|
"in_list_view": 1,
|
||||||
"fieldtype": "Check",
|
"label": "Page Break"
|
||||||
"hidden": 0,
|
},
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Page Break",
|
|
||||||
"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,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"fieldname": "dn_detail",
|
||||||
"bold": 0,
|
"fieldtype": "Data",
|
||||||
"collapsible": 0,
|
"hidden": 1,
|
||||||
"columns": 0,
|
"in_list_view": 1,
|
||||||
"fieldname": "dn_detail",
|
"label": "DN Detail"
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 1,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "DN Detail",
|
|
||||||
"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,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hide_heading": 0,
|
"idx": 1,
|
||||||
"hide_toolbar": 0,
|
"istable": 1,
|
||||||
"idx": 1,
|
"links": [],
|
||||||
"image_view": 0,
|
"modified": "2021-12-14 01:22:00.715935",
|
||||||
"in_create": 0,
|
"modified_by": "Administrator",
|
||||||
|
"module": "Stock",
|
||||||
"is_submittable": 0,
|
"name": "Packing Slip Item",
|
||||||
"issingle": 0,
|
"naming_rule": "Random",
|
||||||
"istable": 1,
|
"owner": "Administrator",
|
||||||
"max_attachments": 0,
|
"permissions": [],
|
||||||
"modified": "2018-06-01 07:21:58.220980",
|
"sort_field": "modified",
|
||||||
"modified_by": "Administrator",
|
"sort_order": "DESC",
|
||||||
"module": "Stock",
|
"track_changes": 1
|
||||||
"name": "Packing Slip Item",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"quick_entry": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"track_changes": 1,
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
@ -59,7 +59,7 @@ frappe.ui.form.on("Quality Inspection", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
item_code: function(frm) {
|
item_code: function(frm) {
|
||||||
if (frm.doc.item_code) {
|
if (frm.doc.item_code && !frm.doc.quality_inspection_template) {
|
||||||
return frm.call({
|
return frm.call({
|
||||||
method: "get_quality_inspection_template",
|
method: "get_quality_inspection_template",
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
|
@ -18,6 +18,15 @@ class QualityInspection(Document):
|
|||||||
if not self.readings and self.item_code:
|
if not self.readings and self.item_code:
|
||||||
self.get_item_specification_details()
|
self.get_item_specification_details()
|
||||||
|
|
||||||
|
if self.inspection_type=="In Process" and self.reference_type=="Job Card":
|
||||||
|
item_qi_template = frappe.db.get_value("Item", self.item_code, 'quality_inspection_template')
|
||||||
|
parameters = get_template_details(item_qi_template)
|
||||||
|
for reading in self.readings:
|
||||||
|
for d in parameters:
|
||||||
|
if reading.specification == d.specification:
|
||||||
|
reading.update(d)
|
||||||
|
reading.status = "Accepted"
|
||||||
|
|
||||||
if self.readings:
|
if self.readings:
|
||||||
self.inspect_and_set_status()
|
self.inspect_and_set_status()
|
||||||
|
|
||||||
|
@ -2,15 +2,17 @@
|
|||||||
"creation": "2021-08-24 17:56:40.754909",
|
"creation": "2021-08-24 17:56:40.754909",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Form Tour",
|
"doctype": "Form Tour",
|
||||||
|
"first_document": 0,
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
|
"include_name_field": 0,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"modified": "2021-08-24 18:04:50.928431",
|
"modified": "2021-11-24 17:59:44.559001",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Item",
|
"name": "Item",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"reference_doctype": "Item",
|
"reference_doctype": "Item",
|
||||||
"save_on_complete": 0,
|
"save_on_complete": 1,
|
||||||
"steps": [
|
"steps": [
|
||||||
{
|
{
|
||||||
"description": "Enter code for Asset Item",
|
"description": "Enter code for Asset Item",
|
||||||
@ -36,14 +38,27 @@
|
|||||||
"position": "Bottom",
|
"position": "Bottom",
|
||||||
"title": "Asset Item Name"
|
"title": "Asset Item Name"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Select an Item Group",
|
||||||
|
"field": "",
|
||||||
|
"fieldname": "item_group",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"has_next_condition": 0,
|
||||||
|
"is_table_field": 0,
|
||||||
|
"label": "Item Group",
|
||||||
|
"parent_field": "",
|
||||||
|
"position": "Right",
|
||||||
|
"title": "Item Group"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Check this field to make this an Asset Item",
|
"description": "Check this field to make this an Asset Item",
|
||||||
"field": "",
|
"field": "",
|
||||||
"fieldname": "is_fixed_asset",
|
"fieldname": "is_fixed_asset",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"has_next_condition": 0,
|
"has_next_condition": 1,
|
||||||
"is_table_field": 0,
|
"is_table_field": 0,
|
||||||
"label": "Is Fixed Asset",
|
"label": "Is Fixed Asset",
|
||||||
|
"next_step_condition": "eval:doc.is_fixed_asset",
|
||||||
"parent_field": "",
|
"parent_field": "",
|
||||||
"position": "Bottom",
|
"position": "Bottom",
|
||||||
"title": "Is this a Fixed Asset?"
|
"title": "Is this a Fixed Asset?"
|
||||||
@ -53,9 +68,10 @@
|
|||||||
"field": "",
|
"field": "",
|
||||||
"fieldname": "auto_create_assets",
|
"fieldname": "auto_create_assets",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"has_next_condition": 0,
|
"has_next_condition": 1,
|
||||||
"is_table_field": 0,
|
"is_table_field": 0,
|
||||||
"label": "Auto Create Assets on Purchase",
|
"label": "Auto Create Assets on Purchase",
|
||||||
|
"next_step_condition": "eval:doc.auto_create_assets",
|
||||||
"parent_field": "",
|
"parent_field": "",
|
||||||
"position": "Bottom",
|
"position": "Bottom",
|
||||||
"title": "Auto Create Asset on Purchase"
|
"title": "Auto Create Asset on Purchase"
|
||||||
@ -69,7 +85,7 @@
|
|||||||
"is_table_field": 0,
|
"is_table_field": 0,
|
||||||
"label": "Asset Category",
|
"label": "Asset Category",
|
||||||
"parent_field": "",
|
"parent_field": "",
|
||||||
"position": "Bottom",
|
"position": "Left",
|
||||||
"title": "Asset Category"
|
"title": "Asset Category"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -81,9 +97,9 @@
|
|||||||
"is_table_field": 0,
|
"is_table_field": 0,
|
||||||
"label": "Asset Naming Series",
|
"label": "Asset Naming Series",
|
||||||
"parent_field": "",
|
"parent_field": "",
|
||||||
"position": "Bottom",
|
"position": "Left",
|
||||||
"title": "Asset Naming Series"
|
"title": "Asset Naming Series"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "Item"
|
"title": "Item"
|
||||||
}
|
}
|
79
erpnext/stock/form_tour/item_general/item_general.json
Normal file
79
erpnext/stock/form_tour/item_general/item_general.json
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"creation": "2021-12-02 10:37:55.433087",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Form Tour",
|
||||||
|
"first_document": 0,
|
||||||
|
"idx": 0,
|
||||||
|
"include_name_field": 0,
|
||||||
|
"is_standard": 1,
|
||||||
|
"modified": "2021-12-02 10:37:55.433087",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Stock",
|
||||||
|
"name": "Item General",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"reference_doctype": "Item",
|
||||||
|
"save_on_complete": 1,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"description": "Enter code for the Item",
|
||||||
|
"field": "",
|
||||||
|
"fieldname": "item_code",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"has_next_condition": 0,
|
||||||
|
"is_table_field": 0,
|
||||||
|
"label": "Item Code",
|
||||||
|
"parent_field": "",
|
||||||
|
"position": "Right",
|
||||||
|
"title": "Item Code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enter name for the Item",
|
||||||
|
"field": "",
|
||||||
|
"fieldname": "item_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"has_next_condition": 0,
|
||||||
|
"is_table_field": 0,
|
||||||
|
"label": "Item Name",
|
||||||
|
"parent_field": "",
|
||||||
|
"position": "Right",
|
||||||
|
"title": "Item Name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Select an Item Group",
|
||||||
|
"field": "",
|
||||||
|
"fieldname": "item_group",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"has_next_condition": 0,
|
||||||
|
"is_table_field": 0,
|
||||||
|
"label": "Item Group",
|
||||||
|
"parent_field": "",
|
||||||
|
"position": "Right",
|
||||||
|
"title": "Item Group"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "This is the default measuring unit that you will use for your product. It could be Nos, Kgs, Meters, etc.",
|
||||||
|
"field": "",
|
||||||
|
"fieldname": "stock_uom",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"has_next_condition": 0,
|
||||||
|
"is_table_field": 0,
|
||||||
|
"label": "Default Unit of Measure",
|
||||||
|
"parent_field": "",
|
||||||
|
"position": "Right",
|
||||||
|
"title": "Default Unit of Measurement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "When creating an Item, entering a value for this field will automatically create an Item Price at the backend. Entering a value after the Item has been saved will not work. In this case, the Item Price is created from any transactions with the Item.",
|
||||||
|
"field": "",
|
||||||
|
"fieldname": "standard_rate",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"has_next_condition": 0,
|
||||||
|
"is_table_field": 0,
|
||||||
|
"label": "Standard Selling Rate",
|
||||||
|
"parent_field": "",
|
||||||
|
"position": "Left",
|
||||||
|
"title": "Standard Selling Rate"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Item General"
|
||||||
|
}
|
@ -48,6 +48,7 @@ def get_item_info(filters):
|
|||||||
conditions = [get_item_group_condition(filters.get("item_group"))]
|
conditions = [get_item_group_condition(filters.get("item_group"))]
|
||||||
if filters.get("brand"):
|
if filters.get("brand"):
|
||||||
conditions.append("item.brand=%(brand)s")
|
conditions.append("item.brand=%(brand)s")
|
||||||
|
conditions.append("is_stock_item = 1")
|
||||||
|
|
||||||
return frappe.db.sql("""select name, item_name, description, brand, item_group,
|
return frappe.db.sql("""select name, item_name, description, brand, item_group,
|
||||||
safety_stock, lead_time_days from `tabItem` item where {}"""
|
safety_stock, lead_time_days from `tabItem` item where {}"""
|
||||||
|
@ -167,7 +167,7 @@ def get_stock_ledger_entries(filters, items):
|
|||||||
sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference,
|
sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference,
|
||||||
sle.item_code as name, sle.voucher_no, sle.stock_value, sle.batch_no
|
sle.item_code as name, sle.voucher_no, sle.stock_value, sle.batch_no
|
||||||
from
|
from
|
||||||
`tabStock Ledger Entry` sle force index (posting_sort_index)
|
`tabStock Ledger Entry` sle
|
||||||
where sle.docstatus < 2 %s %s
|
where sle.docstatus < 2 %s %s
|
||||||
and is_cancelled = 0
|
and is_cancelled = 0
|
||||||
order by sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty""" % #nosec
|
order by sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty""" % #nosec
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
const DIFFERNCE_FIELD_NAMES = [
|
||||||
|
"difference_in_qty",
|
||||||
|
"fifo_qty_diff",
|
||||||
|
"fifo_value_diff",
|
||||||
|
"fifo_valuation_diff",
|
||||||
|
"valuation_diff",
|
||||||
|
"fifo_difference_diff"
|
||||||
|
];
|
||||||
|
|
||||||
|
frappe.query_reports["Stock Ledger Invariant Check"] = {
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"fieldname": "item_code",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Item",
|
||||||
|
"mandatory": 1,
|
||||||
|
"options": "Item",
|
||||||
|
get_query: function() {
|
||||||
|
return {
|
||||||
|
filters: {is_stock_item: 1, has_serial_no: 0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Warehouse",
|
||||||
|
"mandatory": 1,
|
||||||
|
"options": "Warehouse",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
formatter (value, row, column, data, default_formatter) {
|
||||||
|
value = default_formatter(value, row, column, data);
|
||||||
|
if (DIFFERNCE_FIELD_NAMES.includes(column.fieldname) && Math.abs(data[column.fieldname]) > 0.001) {
|
||||||
|
value = "<span style='color:red'>" + value + "</span>";
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 0,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2021-12-16 06:31:23.290916",
|
||||||
|
"disable_prepared_report": 0,
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"modified": "2021-12-16 09:55:58.341764",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Stock",
|
||||||
|
"name": "Stock Ledger Invariant Check",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Stock Ledger Entry",
|
||||||
|
"report_name": "Stock Ledger Invariant Check",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "System Manager"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,236 @@
|
|||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# License: GNU GPL v3. See LICENSE
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
SLE_FIELDS = (
|
||||||
|
"name",
|
||||||
|
"posting_date",
|
||||||
|
"posting_time",
|
||||||
|
"creation",
|
||||||
|
"voucher_type",
|
||||||
|
"voucher_no",
|
||||||
|
"actual_qty",
|
||||||
|
"qty_after_transaction",
|
||||||
|
"incoming_rate",
|
||||||
|
"outgoing_rate",
|
||||||
|
"stock_queue",
|
||||||
|
"batch_no",
|
||||||
|
"stock_value",
|
||||||
|
"stock_value_difference",
|
||||||
|
"valuation_rate",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
columns = get_columns()
|
||||||
|
data = get_data(filters)
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
|
def get_data(filters):
|
||||||
|
sles = get_stock_ledger_entries(filters)
|
||||||
|
return add_invariant_check_fields(sles)
|
||||||
|
|
||||||
|
|
||||||
|
def get_stock_ledger_entries(filters):
|
||||||
|
return frappe.get_all(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
fields=SLE_FIELDS,
|
||||||
|
filters={
|
||||||
|
"item_code": filters.item_code,
|
||||||
|
"warehouse": filters.warehouse,
|
||||||
|
"is_cancelled": 0
|
||||||
|
},
|
||||||
|
order_by="timestamp(posting_date, posting_time), creation",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_invariant_check_fields(sles):
|
||||||
|
balance_qty = 0.0
|
||||||
|
for idx, sle in enumerate(sles):
|
||||||
|
queue = json.loads(sle.stock_queue)
|
||||||
|
|
||||||
|
fifo_qty = 0.0
|
||||||
|
fifo_value = 0.0
|
||||||
|
for qty, rate in queue:
|
||||||
|
fifo_qty += qty
|
||||||
|
fifo_value += qty * rate
|
||||||
|
|
||||||
|
balance_qty += sle.actual_qty
|
||||||
|
if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no:
|
||||||
|
balance_qty = sle.qty_after_transaction
|
||||||
|
|
||||||
|
sle.fifo_queue_qty = fifo_qty
|
||||||
|
sle.fifo_stock_value = fifo_value
|
||||||
|
sle.fifo_valuation_rate = fifo_value / fifo_qty if fifo_qty else None
|
||||||
|
sle.balance_value_by_qty = (
|
||||||
|
sle.stock_value / sle.qty_after_transaction if sle.qty_after_transaction else None
|
||||||
|
)
|
||||||
|
sle.expected_qty_after_transaction = balance_qty
|
||||||
|
|
||||||
|
# set difference fields
|
||||||
|
sle.difference_in_qty = sle.qty_after_transaction - sle.expected_qty_after_transaction
|
||||||
|
sle.fifo_qty_diff = sle.qty_after_transaction - fifo_qty
|
||||||
|
sle.fifo_value_diff = sle.stock_value - fifo_value
|
||||||
|
sle.fifo_valuation_diff = (
|
||||||
|
sle.valuation_rate - sle.fifo_valuation_rate if sle.fifo_valuation_rate else None
|
||||||
|
)
|
||||||
|
sle.valuation_diff = (
|
||||||
|
sle.valuation_rate - sle.balance_value_by_qty if sle.balance_value_by_qty else None
|
||||||
|
)
|
||||||
|
|
||||||
|
if idx > 0:
|
||||||
|
sle.fifo_stock_diff = sle.fifo_stock_value - sles[idx - 1].fifo_stock_value
|
||||||
|
sle.fifo_difference_diff = sle.fifo_stock_diff - sle.stock_value_difference
|
||||||
|
|
||||||
|
return sles
|
||||||
|
|
||||||
|
|
||||||
|
def get_columns():
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"fieldname": "name",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Stock Ledger Entry",
|
||||||
|
"options": "Stock Ledger Entry",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posting_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Posting Date",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posting_time",
|
||||||
|
"fieldtype": "Time",
|
||||||
|
"label": "Posting Time",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "creation",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"label": "Creation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "voucher_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Voucher Type",
|
||||||
|
"options": "DocType",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "voucher_no",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"label": "Voucher No",
|
||||||
|
"options": "voucher_type",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "batch_no",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Batch",
|
||||||
|
"options": "Batch",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "actual_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Qty Change",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "incoming_rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Incoming Rate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "outgoing_rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Outgoing Rate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "qty_after_transaction",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(A) Qty After Transaction",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "expected_qty_after_transaction",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(B) Expected Qty After Transaction",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "difference_in_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "A - B",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "stock_queue",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "FIFO Queue",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_queue_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(C) Total qty in queue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_qty_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "A - C",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "stock_value",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(D) Balance Stock Value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_stock_value",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(E) Balance Stock Value in Queue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_value_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "D - E",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"fieldname": "stock_value_difference",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(F) Stock Value Difference",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_stock_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(G) Stock Value difference (FIFO queue)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_difference_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "F - G",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "valuation_rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(H) Valuation Rate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_valuation_rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(I) Valuation Rate as per FIFO",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_valuation_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "H - I",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "balance_value_by_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(J) Valuation = Value (D) ÷ Qty (A)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "valuation_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "H - J",
|
||||||
|
},
|
||||||
|
]
|
@ -41,6 +41,12 @@ REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [
|
|||||||
("Total Stock Summary", {"group_by": "warehouse",}),
|
("Total Stock Summary", {"group_by": "warehouse",}),
|
||||||
("Batch Item Expiry Status", {}),
|
("Batch Item Expiry Status", {}),
|
||||||
("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}),
|
("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}),
|
||||||
|
("Stock Ledger Invariant Check",
|
||||||
|
{
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"item": "_Test Item"
|
||||||
|
}
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
OPTIONAL_FILTERS = {
|
OPTIONAL_FILTERS = {
|
||||||
|
@ -420,13 +420,13 @@ def handle_status_change(doc, apply_sla_for_resolution):
|
|||||||
if is_open_status(prev_status) and is_hold_status(doc.status):
|
if is_open_status(prev_status) and is_hold_status(doc.status):
|
||||||
# Issue is on hold -> Set on_hold_since
|
# Issue is on hold -> Set on_hold_since
|
||||||
doc.on_hold_since = now_time
|
doc.on_hold_since = now_time
|
||||||
|
reset_expected_response_and_resolution(doc)
|
||||||
|
|
||||||
# Replied to Open
|
# Replied to Open
|
||||||
if is_hold_status(prev_status) and is_open_status(doc.status):
|
if is_hold_status(prev_status) and is_open_status(doc.status):
|
||||||
# Issue was on hold -> Calculate Total Hold Time
|
# Issue was on hold -> Calculate Total Hold Time
|
||||||
calculate_hold_hours()
|
calculate_hold_hours()
|
||||||
# Issue is open -> reset resolution_date
|
# Issue is open -> reset resolution_date
|
||||||
reset_expected_response_and_resolution(doc)
|
|
||||||
reset_resolution_metrics(doc)
|
reset_resolution_metrics(doc)
|
||||||
|
|
||||||
# Open to Closed
|
# Open to Closed
|
||||||
@ -440,7 +440,6 @@ def handle_status_change(doc, apply_sla_for_resolution):
|
|||||||
# Issue was closed -> Calculate Total Hold Time from resolution_date
|
# Issue was closed -> Calculate Total Hold Time from resolution_date
|
||||||
calculate_hold_hours()
|
calculate_hold_hours()
|
||||||
# Issue is open -> reset resolution_date
|
# Issue is open -> reset resolution_date
|
||||||
reset_expected_response_and_resolution(doc)
|
|
||||||
reset_resolution_metrics(doc)
|
reset_resolution_metrics(doc)
|
||||||
|
|
||||||
# Closed to Replied
|
# Closed to Replied
|
||||||
@ -449,6 +448,7 @@ def handle_status_change(doc, apply_sla_for_resolution):
|
|||||||
calculate_hold_hours()
|
calculate_hold_hours()
|
||||||
# Issue is on hold -> Set on_hold_since
|
# Issue is on hold -> Set on_hold_since
|
||||||
doc.on_hold_since = now_time
|
doc.on_hold_since = now_time
|
||||||
|
reset_expected_response_and_resolution(doc)
|
||||||
|
|
||||||
# Replied to Closed
|
# Replied to Closed
|
||||||
if is_hold_status(prev_status) and is_fulfilled_status(doc.status):
|
if is_hold_status(prev_status) and is_fulfilled_status(doc.status):
|
||||||
@ -640,28 +640,35 @@ def on_communication_update(doc, status):
|
|||||||
if not parent.meta.has_field('service_level_agreement'):
|
if not parent.meta.has_field('service_level_agreement'):
|
||||||
return
|
return
|
||||||
|
|
||||||
for_resolution = frappe.db.get_value('Service Level Agreement', parent.service_level_agreement, 'apply_sla_for_resolution')
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
doc.sent_or_received == "Received" # a reply is received
|
doc.sent_or_received == "Received" # a reply is received
|
||||||
and parent.get('status') == 'Open' # issue status is set as open from communication.py
|
and parent.get('status') == 'Open' # issue status is set as open from communication.py
|
||||||
and parent._doc_before_save
|
and parent.get_doc_before_save()
|
||||||
and parent.get('status') != parent._doc_before_save.get('status') # status changed
|
and parent.get('status') != parent._doc_before_save.get('status') # status changed
|
||||||
):
|
):
|
||||||
# undo the status change in db
|
# undo the status change in db
|
||||||
# since prev status is fetched from db
|
# since prev status is fetched from db
|
||||||
frappe.db.set_value(parent.doctype, parent.name, 'status', parent._doc_before_save.get('status'))
|
frappe.db.set_value(
|
||||||
|
parent.doctype, parent.name,
|
||||||
|
'status', parent._doc_before_save.get('status'),
|
||||||
|
update_modified=False
|
||||||
|
)
|
||||||
|
|
||||||
elif (
|
elif (
|
||||||
doc.sent_or_received == "Sent" # a reply is sent
|
doc.sent_or_received == "Sent" # a reply is sent
|
||||||
and parent.get('first_responded_on') # first_responded_on is set from communication.py
|
and parent.get('first_responded_on') # first_responded_on is set from communication.py
|
||||||
and parent._doc_before_save
|
and parent.get_doc_before_save()
|
||||||
and not parent._doc_before_save.get('first_responded_on') # first_responded_on was not set
|
and not parent._doc_before_save.get('first_responded_on') # first_responded_on was not set
|
||||||
):
|
):
|
||||||
# reset first_responded_on since it will be handled/set later on
|
# reset first_responded_on since it will be handled/set later on
|
||||||
parent.first_responded_on = None
|
parent.first_responded_on = None
|
||||||
parent.flags.on_first_reply = True
|
parent.flags.on_first_reply = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
for_resolution = frappe.db.get_value('Service Level Agreement', parent.service_level_agreement, 'apply_sla_for_resolution')
|
||||||
|
|
||||||
handle_status_change(parent, for_resolution)
|
handle_status_change(parent, for_resolution)
|
||||||
update_response_and_resolution_metrics(parent, for_resolution)
|
update_response_and_resolution_metrics(parent, for_resolution)
|
||||||
update_agreement_status(parent, for_resolution)
|
update_agreement_status(parent, for_resolution)
|
||||||
@ -670,12 +677,10 @@ def on_communication_update(doc, status):
|
|||||||
|
|
||||||
|
|
||||||
def reset_expected_response_and_resolution(doc):
|
def reset_expected_response_and_resolution(doc):
|
||||||
update_values = {}
|
|
||||||
if doc.meta.has_field("first_responded_on") and not doc.get('first_responded_on'):
|
if doc.meta.has_field("first_responded_on") and not doc.get('first_responded_on'):
|
||||||
update_values['response_by'] = None
|
doc.response_by = None
|
||||||
if doc.meta.has_field("resolution_by") and not doc.get('resolution_date'):
|
if doc.meta.has_field("resolution_by") and not doc.get('resolution_date'):
|
||||||
update_values['resolution_by'] = None
|
doc.resolution_by = None
|
||||||
doc.db_set(update_values)
|
|
||||||
|
|
||||||
|
|
||||||
def set_response_by(doc, start_date_time, priority):
|
def set_response_by(doc, start_date_time, priority):
|
||||||
|
Loading…
Reference in New Issue
Block a user