mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-01 20:48:27 +00:00
Merge branch 'version-13-hotfix' of https://github.com/frappe/erpnext into tds_validity_v13
This commit is contained in:
8
.snyk
8
.snyk
@@ -1,8 +0,0 @@
|
||||
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
|
||||
version: v1.14.0
|
||||
ignore: {}
|
||||
# patches apply the minimum changes required to fix a vulnerability
|
||||
patch:
|
||||
SNYK-JS-LODASH-450202:
|
||||
- cypress > getos > async > lodash:
|
||||
patched: '2020-01-31T01:35:12.802Z'
|
||||
@@ -219,6 +219,7 @@
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"description": "A customer must have primary contact email.",
|
||||
"fieldname": "primary_mandatory",
|
||||
"fieldtype": "Check",
|
||||
"label": "Send To Primary Contact"
|
||||
@@ -286,7 +287,7 @@
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2021-05-21 10:14:22.426672",
|
||||
"modified": "2021-09-06 21:00:45.732505",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Statement Of Accounts",
|
||||
|
||||
@@ -196,7 +196,10 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory):
|
||||
primary_email = customer.get('email_id') or ''
|
||||
billing_email = get_customer_emails(customer.name, 1, billing_and_primary=False)
|
||||
|
||||
if billing_email == '' or (primary_email == '' and int(primary_mandatory)):
|
||||
if int(primary_mandatory):
|
||||
if (primary_email == ''):
|
||||
continue
|
||||
elif (billing_email == '') and (primary_email == ''):
|
||||
continue
|
||||
|
||||
customer_list.append({
|
||||
@@ -208,10 +211,29 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=True):
|
||||
""" Returns first email from Contact Email table as a Billing email
|
||||
when Is Billing Contact checked
|
||||
and Primary email- email with Is Primary checked """
|
||||
|
||||
billing_email = frappe.db.sql("""
|
||||
SELECT c.email_id FROM `tabContact` AS c JOIN `tabDynamic Link` AS l ON c.name=l.parent
|
||||
WHERE l.link_doctype='Customer' and l.link_name=%s and c.is_billing_contact=1
|
||||
order by c.creation desc""", customer_name)
|
||||
SELECT
|
||||
email.email_id
|
||||
FROM
|
||||
`tabContact Email` AS email
|
||||
JOIN
|
||||
`tabDynamic Link` AS link
|
||||
ON
|
||||
email.parent=link.parent
|
||||
JOIN
|
||||
`tabContact` AS contact
|
||||
ON
|
||||
contact.name=link.parent
|
||||
WHERE
|
||||
link.link_doctype='Customer'
|
||||
and link.link_name=%s
|
||||
and contact.is_billing_contact=1
|
||||
ORDER BY
|
||||
contact.creation desc""", customer_name)
|
||||
|
||||
if len(billing_email) == 0 or (billing_email[0][0] is None):
|
||||
if billing_and_primary:
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
frappe.query_reports["Accounts Payable"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"fieldname": "company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
@@ -12,19 +12,19 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
"default": frappe.defaults.get_user_default("Company")
|
||||
},
|
||||
{
|
||||
"fieldname":"report_date",
|
||||
"fieldname": "report_date",
|
||||
"label": __("Posting Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.get_today()
|
||||
},
|
||||
{
|
||||
"fieldname":"finance_book",
|
||||
"fieldname": "finance_book",
|
||||
"label": __("Finance Book"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"fieldname":"cost_center",
|
||||
"fieldname": "cost_center",
|
||||
"label": __("Cost Center"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Cost Center",
|
||||
@@ -38,7 +38,7 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"supplier",
|
||||
"fieldname": "supplier",
|
||||
"label": __("Supplier"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier",
|
||||
@@ -54,48 +54,48 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"ageing_based_on",
|
||||
"fieldname": "ageing_based_on",
|
||||
"label": __("Ageing Based On"),
|
||||
"fieldtype": "Select",
|
||||
"options": 'Posting Date\nDue Date\nSupplier Invoice Date',
|
||||
"default": "Due Date"
|
||||
},
|
||||
{
|
||||
"fieldname":"range1",
|
||||
"fieldname": "range1",
|
||||
"label": __("Ageing Range 1"),
|
||||
"fieldtype": "Int",
|
||||
"default": "30",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"range2",
|
||||
"fieldname": "range2",
|
||||
"label": __("Ageing Range 2"),
|
||||
"fieldtype": "Int",
|
||||
"default": "60",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"range3",
|
||||
"fieldname": "range3",
|
||||
"label": __("Ageing Range 3"),
|
||||
"fieldtype": "Int",
|
||||
"default": "90",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"range4",
|
||||
"fieldname": "range4",
|
||||
"label": __("Ageing Range 4"),
|
||||
"fieldtype": "Int",
|
||||
"default": "120",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"payment_terms_template",
|
||||
"fieldname": "payment_terms_template",
|
||||
"label": __("Payment Terms Template"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Payment Terms Template"
|
||||
},
|
||||
{
|
||||
"fieldname":"supplier_group",
|
||||
"fieldname": "supplier_group",
|
||||
"label": __("Supplier Group"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier Group"
|
||||
@@ -106,12 +106,17 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
"fieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"fieldname":"based_on_payment_terms",
|
||||
"fieldname": "based_on_payment_terms",
|
||||
"label": __("Based On Payment Terms"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname":"tax_id",
|
||||
"fieldname": "show_remarks",
|
||||
"label": __("Show Remarks"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_id",
|
||||
"label": __("Tax Id"),
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
frappe.query_reports["Accounts Receivable"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"fieldname": "company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
@@ -12,19 +12,19 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
"default": frappe.defaults.get_user_default("Company")
|
||||
},
|
||||
{
|
||||
"fieldname":"report_date",
|
||||
"fieldname": "report_date",
|
||||
"label": __("Posting Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.get_today()
|
||||
},
|
||||
{
|
||||
"fieldname":"finance_book",
|
||||
"fieldname": "finance_book",
|
||||
"label": __("Finance Book"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"fieldname":"cost_center",
|
||||
"fieldname": "cost_center",
|
||||
"label": __("Cost Center"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Cost Center",
|
||||
@@ -38,7 +38,7 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"customer",
|
||||
"fieldname": "customer",
|
||||
"label": __("Customer"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer",
|
||||
@@ -67,66 +67,66 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"ageing_based_on",
|
||||
"fieldname": "ageing_based_on",
|
||||
"label": __("Ageing Based On"),
|
||||
"fieldtype": "Select",
|
||||
"options": 'Posting Date\nDue Date',
|
||||
"default": "Due Date"
|
||||
},
|
||||
{
|
||||
"fieldname":"range1",
|
||||
"fieldname": "range1",
|
||||
"label": __("Ageing Range 1"),
|
||||
"fieldtype": "Int",
|
||||
"default": "30",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"range2",
|
||||
"fieldname": "range2",
|
||||
"label": __("Ageing Range 2"),
|
||||
"fieldtype": "Int",
|
||||
"default": "60",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"range3",
|
||||
"fieldname": "range3",
|
||||
"label": __("Ageing Range 3"),
|
||||
"fieldtype": "Int",
|
||||
"default": "90",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"range4",
|
||||
"fieldname": "range4",
|
||||
"label": __("Ageing Range 4"),
|
||||
"fieldtype": "Int",
|
||||
"default": "120",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"customer_group",
|
||||
"fieldname": "customer_group",
|
||||
"label": __("Customer Group"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer Group"
|
||||
},
|
||||
{
|
||||
"fieldname":"payment_terms_template",
|
||||
"fieldname": "payment_terms_template",
|
||||
"label": __("Payment Terms Template"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Payment Terms Template"
|
||||
},
|
||||
{
|
||||
"fieldname":"sales_partner",
|
||||
"fieldname": "sales_partner",
|
||||
"label": __("Sales Partner"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Partner"
|
||||
},
|
||||
{
|
||||
"fieldname":"sales_person",
|
||||
"fieldname": "sales_person",
|
||||
"label": __("Sales Person"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Person"
|
||||
},
|
||||
{
|
||||
"fieldname":"territory",
|
||||
"fieldname": "territory",
|
||||
"label": __("Territory"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Territory"
|
||||
@@ -137,45 +137,50 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
"fieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"fieldname":"based_on_payment_terms",
|
||||
"fieldname": "based_on_payment_terms",
|
||||
"label": __("Based On Payment Terms"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname":"show_future_payments",
|
||||
"fieldname": "show_future_payments",
|
||||
"label": __("Show Future Payments"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname":"show_delivery_notes",
|
||||
"fieldname": "show_delivery_notes",
|
||||
"label": __("Show Linked Delivery Notes"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname":"show_sales_person",
|
||||
"fieldname": "show_sales_person",
|
||||
"label": __("Show Sales Person"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname":"tax_id",
|
||||
"fieldname": "show_remarks",
|
||||
"label": __("Show Remarks"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_id",
|
||||
"label": __("Tax Id"),
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"customer_name",
|
||||
"fieldname": "customer_name",
|
||||
"label": __("Customer Name"),
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"payment_terms",
|
||||
"fieldname": "payment_terms",
|
||||
"label": __("Payment Tems"),
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"credit_limit",
|
||||
"fieldname": "credit_limit",
|
||||
"label": __("Credit Limit"),
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1
|
||||
|
||||
@@ -106,6 +106,7 @@ class ReceivablePayableReport(object):
|
||||
party = gle.party,
|
||||
posting_date = gle.posting_date,
|
||||
account_currency = gle.account_currency,
|
||||
remarks = gle.remarks if self.filters.get("show_remarks") else None,
|
||||
invoiced = 0.0,
|
||||
paid = 0.0,
|
||||
credit_note = 0.0,
|
||||
@@ -583,10 +584,12 @@ class ReceivablePayableReport(object):
|
||||
else:
|
||||
select_fields = "debit, credit"
|
||||
|
||||
remarks = ", remarks" if self.filters.get("show_remarks") else ""
|
||||
|
||||
self.gl_entries = frappe.db.sql("""
|
||||
select
|
||||
name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center,
|
||||
against_voucher_type, against_voucher, account_currency, {0}
|
||||
against_voucher_type, against_voucher, account_currency, {0} {remarks}
|
||||
from
|
||||
`tabGL Entry`
|
||||
where
|
||||
@@ -595,7 +598,7 @@ class ReceivablePayableReport(object):
|
||||
and party_type=%s
|
||||
and (party is not null and party != '')
|
||||
{1} {2} {3}"""
|
||||
.format(select_fields, date_condition, conditions, order_by), values, as_dict=True)
|
||||
.format(select_fields, date_condition, conditions, order_by, remarks=remarks), values, as_dict=True)
|
||||
|
||||
def get_sales_invoices_or_customers_based_on_sales_person(self):
|
||||
if self.filters.get("sales_person"):
|
||||
@@ -754,6 +757,10 @@ class ReceivablePayableReport(object):
|
||||
self.add_column(label=_('Voucher Type'), fieldname='voucher_type', fieldtype='Data')
|
||||
self.add_column(label=_('Voucher No'), fieldname='voucher_no', fieldtype='Dynamic Link',
|
||||
options='voucher_type', width=180)
|
||||
|
||||
if self.filters.show_remarks:
|
||||
self.add_column(label=_('Remarks'), fieldname='remarks', fieldtype='Text', width=200),
|
||||
|
||||
self.add_column(label='Due Date', fieldtype='Date')
|
||||
|
||||
if self.party_type == "Supplier":
|
||||
|
||||
@@ -425,7 +425,10 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
status: ["!=", "Stopped"],
|
||||
per_ordered: ["<", 100],
|
||||
company: me.frm.doc.company
|
||||
}
|
||||
},
|
||||
allow_child_item_selection: true,
|
||||
child_fielname: "items",
|
||||
child_columns: ["item_code", "qty"]
|
||||
})
|
||||
}, __("Get Items From"));
|
||||
|
||||
|
||||
@@ -307,7 +307,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
cond = ''
|
||||
if filters.get('customer'):
|
||||
if filters and filters.get('customer'):
|
||||
cond = """(`tabProject`.customer = %s or
|
||||
ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer")))
|
||||
|
||||
|
||||
87
erpnext/controllers/tests/test_queries.py
Normal file
87
erpnext/controllers/tests/test_queries.py
Normal file
@@ -0,0 +1,87 @@
|
||||
import unittest
|
||||
from functools import partial
|
||||
|
||||
from erpnext.controllers import queries
|
||||
|
||||
|
||||
def add_default_params(func, doctype):
|
||||
return partial(
|
||||
func, doctype=doctype, txt="", searchfield="name", start=0, page_len=20, filters=None
|
||||
)
|
||||
|
||||
|
||||
class TestQueries(unittest.TestCase):
|
||||
|
||||
# All tests are based on doctype/test_records.json
|
||||
|
||||
def assert_nested_in(self, item, container):
|
||||
self.assertIn(item, [vals for tuples in container for vals in tuples])
|
||||
|
||||
def test_employee_query(self):
|
||||
query = add_default_params(queries.employee_query, "Employee")
|
||||
|
||||
self.assertGreaterEqual(len(query(txt="_Test Employee")), 3)
|
||||
self.assertGreaterEqual(len(query(txt="_Test Employee 1")), 1)
|
||||
|
||||
def test_lead_query(self):
|
||||
query = add_default_params(queries.lead_query, "Lead")
|
||||
|
||||
self.assertGreaterEqual(len(query(txt="_Test Lead")), 4)
|
||||
self.assertEqual(len(query(txt="_Test Lead 4")), 1)
|
||||
|
||||
def test_customer_query(self):
|
||||
query = add_default_params(queries.customer_query, "Customer")
|
||||
|
||||
self.assertGreaterEqual(len(query(txt="_Test Customer")), 7)
|
||||
self.assertGreaterEqual(len(query(txt="_Test Customer USD")), 1)
|
||||
|
||||
def test_supplier_query(self):
|
||||
query = add_default_params(queries.supplier_query, "Supplier")
|
||||
|
||||
self.assertGreaterEqual(len(query(txt="_Test Supplier")), 7)
|
||||
self.assertGreaterEqual(len(query(txt="_Test Supplier USD")), 1)
|
||||
|
||||
def test_item_query(self):
|
||||
query = add_default_params(queries.item_query, "Item")
|
||||
|
||||
self.assertGreaterEqual(len(query(txt="_Test Item")), 7)
|
||||
self.assertEqual(len(query(txt="_Test Item Home Desktop 100 3")), 1)
|
||||
|
||||
fg_item = "_Test FG Item"
|
||||
stock_items = query(txt=fg_item, filters={"is_stock_item": 1})
|
||||
self.assert_nested_in("_Test FG Item", stock_items)
|
||||
|
||||
bundled_stock_items = query(txt="_test product bundle item 5", filters={"is_stock_item": 1})
|
||||
self.assertEqual(len(bundled_stock_items), 0)
|
||||
|
||||
def test_bom_qury(self):
|
||||
query = add_default_params(queries.bom, "BOM")
|
||||
|
||||
self.assertGreaterEqual(len(query(txt="_Test Item Home Desktop Manufactured")), 1)
|
||||
|
||||
def test_project_query(self):
|
||||
query = add_default_params(queries.get_project_name, "BOM")
|
||||
|
||||
self.assertGreaterEqual(len(query(txt="_Test Project")), 1)
|
||||
|
||||
def test_account_query(self):
|
||||
query = add_default_params(queries.get_account_list, "Account")
|
||||
|
||||
debtor_accounts = query(txt="Debtors", filters={"company": "_Test Company"})
|
||||
self.assert_nested_in("Debtors - _TC", debtor_accounts)
|
||||
|
||||
def test_income_account_query(self):
|
||||
query = add_default_params(queries.get_income_account, "Account")
|
||||
|
||||
self.assertGreaterEqual(len(query(filters={"company": "_Test Company"})), 1)
|
||||
|
||||
def test_expense_account_query(self):
|
||||
query = add_default_params(queries.get_expense_account, "Account")
|
||||
|
||||
self.assertGreaterEqual(len(query(filters={"company": "_Test Company"})), 1)
|
||||
|
||||
def test_warehouse_query(self):
|
||||
query = add_default_params(queries.warehouse_query, "Account")
|
||||
|
||||
wh = query(filters=[["Bin", "item_code", "=", "_Test Item"]])
|
||||
self.assertGreaterEqual(len(wh), 1)
|
||||
@@ -138,7 +138,7 @@ class WebsiteItem(WebsiteGenerator):
|
||||
self.website_image = None
|
||||
|
||||
def make_thumbnail(self):
|
||||
if frappe.flags.in_import:
|
||||
if frappe.flags.in_import or frappe.flags.in_migrate:
|
||||
return
|
||||
|
||||
"""Make a thumbnail of `website_image`"""
|
||||
|
||||
@@ -4,6 +4,7 @@ import frappe
|
||||
import taxjar
|
||||
from frappe import _
|
||||
from frappe.contacts.doctype.address.address import get_company_address
|
||||
from frappe.utils import cint
|
||||
|
||||
from erpnext import get_default_company
|
||||
|
||||
@@ -14,6 +15,10 @@ TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_cal
|
||||
SUPPORTED_COUNTRY_CODES = ["AT", "AU", "BE", "BG", "CA", "CY", "CZ", "DE", "DK", "EE", "ES", "FI",
|
||||
"FR", "GB", "GR", "HR", "HU", "IE", "IT", "LT", "LU", "LV", "MT", "NL", "PL", "PT", "RO",
|
||||
"SE", "SI", "SK", "US"]
|
||||
SUPPORTED_STATE_CODES = ['AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'DC', 'FL', 'GA', 'HI', 'ID', 'IL',
|
||||
'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE',
|
||||
'NV', 'NH', 'NJ', 'NM', 'NY', 'NC', 'ND', 'OH', 'OK', 'OR', 'PA', 'RI', 'SC', 'SD',
|
||||
'TN', 'TX', 'UT', 'VT', 'VA', 'WA', 'WV', 'WI', 'WY']
|
||||
|
||||
|
||||
def get_client():
|
||||
@@ -27,7 +32,11 @@ def get_client():
|
||||
api_url = taxjar.SANDBOX_API_URL
|
||||
|
||||
if api_key and api_url:
|
||||
return taxjar.Client(api_key=api_key, api_url=api_url)
|
||||
client = taxjar.Client(api_key=api_key, api_url=api_url)
|
||||
client.set_api_config('headers', {
|
||||
'x-api-version': '2020-08-07'
|
||||
})
|
||||
return client
|
||||
|
||||
|
||||
def create_transaction(doc, method):
|
||||
@@ -57,7 +66,10 @@ def create_transaction(doc, method):
|
||||
tax_dict['amount'] = doc.total + tax_dict['shipping']
|
||||
|
||||
try:
|
||||
client.create_order(tax_dict)
|
||||
if doc.is_return:
|
||||
client.create_refund(tax_dict)
|
||||
else:
|
||||
client.create_order(tax_dict)
|
||||
except taxjar.exceptions.TaxJarResponseError as err:
|
||||
frappe.throw(_(sanitize_error_response(err)))
|
||||
except Exception as ex:
|
||||
@@ -89,13 +101,15 @@ def get_tax_data(doc):
|
||||
to_country_code = frappe.db.get_value("Country", to_address.country, "code")
|
||||
to_country_code = to_country_code.upper()
|
||||
|
||||
if to_country_code not in SUPPORTED_COUNTRY_CODES:
|
||||
return
|
||||
|
||||
shipping = sum([tax.tax_amount for tax in doc.taxes if tax.account_head == SHIP_ACCOUNT_HEAD])
|
||||
|
||||
if to_shipping_state is not None:
|
||||
to_shipping_state = get_iso_3166_2_state_code(to_address)
|
||||
line_items = [get_line_item_dict(item) for item in doc.items]
|
||||
|
||||
if from_shipping_state not in SUPPORTED_STATE_CODES:
|
||||
from_shipping_state = get_state_code(from_address, 'Company')
|
||||
|
||||
if to_shipping_state not in SUPPORTED_STATE_CODES:
|
||||
to_shipping_state = get_state_code(to_address, 'Shipping')
|
||||
|
||||
tax_dict = {
|
||||
'from_country': from_country_code,
|
||||
@@ -109,11 +123,29 @@ def get_tax_data(doc):
|
||||
'to_street': to_address.address_line1,
|
||||
'to_state': to_shipping_state,
|
||||
'shipping': shipping,
|
||||
'amount': doc.net_total
|
||||
'amount': doc.net_total,
|
||||
'plugin': 'erpnext',
|
||||
'line_items': line_items
|
||||
}
|
||||
|
||||
return tax_dict
|
||||
|
||||
def get_state_code(address, location):
|
||||
if address is not None:
|
||||
state_code = get_iso_3166_2_state_code(address)
|
||||
if state_code not in SUPPORTED_STATE_CODES:
|
||||
frappe.throw(_("Please enter a valid State in the {0} Address").format(location))
|
||||
else:
|
||||
frappe.throw(_("Please enter a valid State in the {0} Address").format(location))
|
||||
|
||||
return state_code
|
||||
|
||||
def get_line_item_dict(item):
|
||||
return dict(
|
||||
id = item.get('idx'),
|
||||
quantity = item.get('qty'),
|
||||
unit_price = item.get('rate'),
|
||||
product_tax_code = item.get('product_tax_category')
|
||||
)
|
||||
|
||||
def set_sales_tax(doc, method):
|
||||
if not TAXJAR_CALCULATE_TAX:
|
||||
@@ -122,17 +154,7 @@ def set_sales_tax(doc, method):
|
||||
if not doc.items:
|
||||
return
|
||||
|
||||
# if the party is exempt from sales tax, then set all tax account heads to zero
|
||||
sales_tax_exempted = hasattr(doc, "exempt_from_sales_tax") and doc.exempt_from_sales_tax \
|
||||
or frappe.db.has_column("Customer", "exempt_from_sales_tax") and frappe.db.get_value("Customer", doc.customer, "exempt_from_sales_tax")
|
||||
|
||||
if sales_tax_exempted:
|
||||
for tax in doc.taxes:
|
||||
if tax.account_head == TAX_ACCOUNT_HEAD:
|
||||
tax.tax_amount = 0
|
||||
break
|
||||
|
||||
doc.run_method("calculate_taxes_and_totals")
|
||||
if check_sales_tax_exemption(doc):
|
||||
return
|
||||
|
||||
tax_dict = get_tax_data(doc)
|
||||
@@ -143,7 +165,6 @@ def set_sales_tax(doc, method):
|
||||
return
|
||||
|
||||
tax_data = validate_tax_request(tax_dict)
|
||||
|
||||
if tax_data is not None:
|
||||
if not tax_data.amount_to_collect:
|
||||
setattr(doc, "taxes", [tax for tax in doc.taxes if tax.account_head != TAX_ACCOUNT_HEAD])
|
||||
@@ -163,9 +184,28 @@ def set_sales_tax(doc, method):
|
||||
"account_head": TAX_ACCOUNT_HEAD,
|
||||
"tax_amount": tax_data.amount_to_collect
|
||||
})
|
||||
# Assigning values to tax_collectable and taxable_amount fields in sales item table
|
||||
for item in tax_data.breakdown.line_items:
|
||||
doc.get('items')[cint(item.id)-1].tax_collectable = item.tax_collectable
|
||||
doc.get('items')[cint(item.id)-1].taxable_amount = item.taxable_amount
|
||||
|
||||
doc.run_method("calculate_taxes_and_totals")
|
||||
|
||||
def check_sales_tax_exemption(doc):
|
||||
# if the party is exempt from sales tax, then set all tax account heads to zero
|
||||
sales_tax_exempted = hasattr(doc, "exempt_from_sales_tax") and doc.exempt_from_sales_tax \
|
||||
or frappe.db.has_column("Customer", "exempt_from_sales_tax") \
|
||||
and frappe.db.get_value("Customer", doc.customer, "exempt_from_sales_tax")
|
||||
|
||||
if sales_tax_exempted:
|
||||
for tax in doc.taxes:
|
||||
if tax.account_head == TAX_ACCOUNT_HEAD:
|
||||
tax.tax_amount = 0
|
||||
break
|
||||
doc.run_method("calculate_taxes_and_totals")
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def validate_tax_request(tax_dict):
|
||||
"""Return the sales tax that should be collected for a given order."""
|
||||
@@ -200,6 +240,8 @@ def get_shipping_address_details(doc):
|
||||
|
||||
if doc.shipping_address_name:
|
||||
shipping_address = frappe.get_doc("Address", doc.shipping_address_name)
|
||||
elif doc.customer_address:
|
||||
shipping_address = frappe.get_doc("Address", doc.customer_address_name)
|
||||
else:
|
||||
shipping_address = get_company_address_details(doc)
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ frappe.ui.form.on('Employee Advance', {
|
||||
frm.trigger('make_return_entry');
|
||||
}, __('Create'));
|
||||
} else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")) {
|
||||
frm.add_custom_button(__("Deduction from salary"), function() {
|
||||
frm.add_custom_button(__("Deduction from Salary"), function() {
|
||||
frm.events.make_deduction_via_additional_salary(frm);
|
||||
}, __('Create'));
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
"default": "0",
|
||||
"fieldname": "repay_unclaimed_amount_from_salary",
|
||||
"fieldtype": "Check",
|
||||
"label": "Repay unclaimed amount from salary"
|
||||
"label": "Repay Unclaimed Amount from Salary"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:cur_frm.doc.employee",
|
||||
@@ -200,7 +200,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-31 22:31:53.746659",
|
||||
"modified": "2021-09-11 18:38:38.617478",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee Advance",
|
||||
|
||||
@@ -172,7 +172,10 @@ def get_paying_amount_paying_exchange_rate(payment_account, doc):
|
||||
@frappe.whitelist()
|
||||
def create_return_through_additional_salary(doc):
|
||||
import json
|
||||
doc = frappe._dict(json.loads(doc))
|
||||
|
||||
if isinstance(doc, str):
|
||||
doc = frappe._dict(json.loads(doc))
|
||||
|
||||
additional_salary = frappe.new_doc('Additional Salary')
|
||||
additional_salary.employee = doc.employee
|
||||
additional_salary.currency = doc.currency
|
||||
|
||||
@@ -12,8 +12,11 @@ import erpnext
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
from erpnext.hr.doctype.employee_advance.employee_advance import (
|
||||
EmployeeAdvanceOverPayment,
|
||||
create_return_through_additional_salary,
|
||||
make_bank_entry,
|
||||
)
|
||||
from erpnext.payroll.doctype.salary_component.test_salary_component import create_salary_component
|
||||
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
|
||||
|
||||
|
||||
class TestEmployeeAdvance(unittest.TestCase):
|
||||
@@ -33,6 +36,46 @@ class TestEmployeeAdvance(unittest.TestCase):
|
||||
journal_entry1 = make_payment_entry(advance)
|
||||
self.assertRaises(EmployeeAdvanceOverPayment, journal_entry1.submit)
|
||||
|
||||
def test_repay_unclaimed_amount_from_salary(self):
|
||||
employee_name = make_employee("_T@employe.advance")
|
||||
advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1})
|
||||
|
||||
args = {"type": "Deduction"}
|
||||
create_salary_component("Advance Salary - Deduction", **args)
|
||||
make_salary_structure("Test Additional Salary for Advance Return", "Monthly", employee=employee_name)
|
||||
|
||||
# additional salary for 700 first
|
||||
advance.reload()
|
||||
additional_salary = create_return_through_additional_salary(advance)
|
||||
additional_salary.salary_component = "Advance Salary - Deduction"
|
||||
additional_salary.payroll_date = nowdate()
|
||||
additional_salary.amount = 700
|
||||
additional_salary.insert()
|
||||
additional_salary.submit()
|
||||
|
||||
advance.reload()
|
||||
self.assertEqual(advance.return_amount, 700)
|
||||
|
||||
# additional salary for remaining 300
|
||||
additional_salary = create_return_through_additional_salary(advance)
|
||||
additional_salary.salary_component = "Advance Salary - Deduction"
|
||||
additional_salary.payroll_date = nowdate()
|
||||
additional_salary.amount = 300
|
||||
additional_salary.insert()
|
||||
additional_salary.submit()
|
||||
|
||||
advance.reload()
|
||||
self.assertEqual(advance.return_amount, 1000)
|
||||
|
||||
# update advance return amount on additional salary cancellation
|
||||
additional_salary.cancel()
|
||||
advance.reload()
|
||||
self.assertEqual(advance.return_amount, 700)
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
|
||||
def make_payment_entry(advance):
|
||||
journal_entry = frappe.get_doc(make_bank_entry("Employee Advance", advance.name))
|
||||
journal_entry.cheque_no = "123123"
|
||||
@@ -41,7 +84,7 @@ def make_payment_entry(advance):
|
||||
|
||||
return journal_entry
|
||||
|
||||
def make_employee_advance(employee_name):
|
||||
def make_employee_advance(employee_name, args=None):
|
||||
doc = frappe.new_doc("Employee Advance")
|
||||
doc.employee = employee_name
|
||||
doc.company = "_Test company"
|
||||
@@ -51,6 +94,10 @@ def make_employee_advance(employee_name):
|
||||
doc.advance_amount = 1000
|
||||
doc.posting_date = nowdate()
|
||||
doc.advance_account = "_Test Employee Advance - _TC"
|
||||
|
||||
if args:
|
||||
doc.update(args)
|
||||
|
||||
doc.insert()
|
||||
doc.submit()
|
||||
|
||||
|
||||
@@ -148,7 +148,10 @@ def set_employee_name(doc):
|
||||
def update_employee(employee, details, date=None, cancel=False):
|
||||
internal_work_history = {}
|
||||
for item in details:
|
||||
fieldtype = frappe.get_meta("Employee").get_field(item.fieldname).fieldtype
|
||||
field = frappe.get_meta("Employee").get_field(item.fieldname)
|
||||
if not field:
|
||||
continue
|
||||
fieldtype = field.fieldtype
|
||||
new_data = item.new if not cancel else item.current
|
||||
if fieldtype == "Date" and new_data:
|
||||
new_data = getdate(new_data)
|
||||
|
||||
@@ -31,8 +31,8 @@ frappe.ui.form.on('Maintenance Visit', {
|
||||
},
|
||||
onload: function (frm, cdt, cdn) {
|
||||
let item = locals[cdt][cdn];
|
||||
if (frm.maintenance_type == 'Scheduled') {
|
||||
let schedule_id = item.purposes[0].prevdoc_detail_docname;
|
||||
if (frm.doc.maintenance_type === "Scheduled") {
|
||||
const schedule_id = item.purposes[0].prevdoc_detail_docname || frm.doc.maintenance_schedule_detail;
|
||||
frappe.call({
|
||||
method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos",
|
||||
args: {
|
||||
@@ -43,6 +43,9 @@ frappe.ui.form.on('Maintenance Visit', {
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
frm.clear_table("purposes");
|
||||
}
|
||||
|
||||
if (!frm.doc.status) {
|
||||
frm.set_value({ status: 'Draft' });
|
||||
|
||||
@@ -91,7 +91,7 @@ class JobCard(Document):
|
||||
if args.get("employee"):
|
||||
# override capacity for employee
|
||||
production_capacity = 1
|
||||
validate_overlap_for = " and jc.employee = %(employee)s "
|
||||
validate_overlap_for = " and jctl.employee = %(employee)s "
|
||||
|
||||
extra_cond = ''
|
||||
if check_next_available_slot:
|
||||
|
||||
@@ -8,71 +8,91 @@ import unittest
|
||||
import frappe
|
||||
from frappe.utils import random_string
|
||||
|
||||
from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError
|
||||
from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError, OverlapError
|
||||
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
|
||||
|
||||
|
||||
class TestJobCard(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.work_order = make_wo_order_test_record(item="_Test FG Item 2", qty=2)
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_job_card(self):
|
||||
data = frappe.get_cached_value('BOM',
|
||||
{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
|
||||
|
||||
if data:
|
||||
bom, bom_item = data
|
||||
job_cards = frappe.get_all('Job Card',
|
||||
filters = {'work_order': self.work_order.name}, fields = ["operation_id", "name"])
|
||||
|
||||
work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
|
||||
if job_cards:
|
||||
job_card = job_cards[0]
|
||||
frappe.db.set_value("Job Card", job_card.name, "operation_row_number", job_card.operation_id)
|
||||
|
||||
job_cards = frappe.get_all('Job Card',
|
||||
filters = {'work_order': work_order.name}, fields = ["operation_id", "name"])
|
||||
doc = frappe.get_doc("Job Card", job_card.name)
|
||||
doc.operation_id = "Test Data"
|
||||
self.assertRaises(OperationMismatchError, doc.save)
|
||||
|
||||
if job_cards:
|
||||
job_card = job_cards[0]
|
||||
frappe.db.set_value("Job Card", job_card.name, "operation_row_number", job_card.operation_id)
|
||||
|
||||
doc = frappe.get_doc("Job Card", job_card.name)
|
||||
doc.operation_id = "Test Data"
|
||||
self.assertRaises(OperationMismatchError, doc.save)
|
||||
|
||||
for d in job_cards:
|
||||
frappe.delete_doc("Job Card", d.name)
|
||||
for d in job_cards:
|
||||
frappe.delete_doc("Job Card", d.name)
|
||||
|
||||
def test_job_card_with_different_work_station(self):
|
||||
data = frappe.get_cached_value('BOM',
|
||||
{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
|
||||
job_cards = frappe.get_all('Job Card',
|
||||
filters = {'work_order': self.work_order.name},
|
||||
fields = ["operation_id", "workstation", "name", "for_quantity"])
|
||||
|
||||
if data:
|
||||
bom, bom_item = data
|
||||
job_card = job_cards[0]
|
||||
|
||||
work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
|
||||
if job_card:
|
||||
workstation = frappe.db.get_value("Workstation",
|
||||
{"name": ("not in", [job_card.workstation])}, "name")
|
||||
|
||||
job_cards = frappe.get_all('Job Card',
|
||||
filters = {'work_order': work_order.name},
|
||||
fields = ["operation_id", "workstation", "name", "for_quantity"])
|
||||
if not workstation or job_card.workstation == workstation:
|
||||
workstation = make_workstation(workstation_name=random_string(5)).name
|
||||
|
||||
job_card = job_cards[0]
|
||||
doc = frappe.get_doc("Job Card", job_card.name)
|
||||
doc.workstation = workstation
|
||||
doc.append("time_logs", {
|
||||
"from_time": "2009-01-01 12:06:25",
|
||||
"to_time": "2009-01-01 12:37:25",
|
||||
"time_in_mins": "31.00002",
|
||||
"completed_qty": job_card.for_quantity
|
||||
})
|
||||
doc.submit()
|
||||
|
||||
if job_card:
|
||||
workstation = frappe.db.get_value("Workstation",
|
||||
{"name": ("not in", [job_card.workstation])}, "name")
|
||||
completed_qty = frappe.db.get_value("Work Order Operation", job_card.operation_id, "completed_qty")
|
||||
self.assertEqual(completed_qty, job_card.for_quantity)
|
||||
|
||||
if not workstation or job_card.workstation == workstation:
|
||||
workstation = make_workstation(workstation_name=random_string(5)).name
|
||||
|
||||
doc = frappe.get_doc("Job Card", job_card.name)
|
||||
doc.workstation = workstation
|
||||
doc.append("time_logs", {
|
||||
"from_time": "2009-01-01 12:06:25",
|
||||
"to_time": "2009-01-01 12:37:25",
|
||||
"time_in_mins": "31.00002",
|
||||
"completed_qty": job_card.for_quantity
|
||||
})
|
||||
doc.submit()
|
||||
|
||||
completed_qty = frappe.db.get_value("Work Order Operation", job_card.operation_id, "completed_qty")
|
||||
self.assertEqual(completed_qty, job_card.for_quantity)
|
||||
|
||||
doc.cancel()
|
||||
doc.cancel()
|
||||
|
||||
for d in job_cards:
|
||||
frappe.delete_doc("Job Card", d.name)
|
||||
|
||||
def test_job_card_overlap(self):
|
||||
wo2 = make_wo_order_test_record(item="_Test FG Item 2", qty=2)
|
||||
|
||||
jc1_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name})
|
||||
jc2_name = frappe.db.get_value("Job Card", {'work_order': wo2.name})
|
||||
|
||||
jc1 = frappe.get_doc("Job Card", jc1_name)
|
||||
jc2 = frappe.get_doc("Job Card", jc2_name)
|
||||
|
||||
employee = "_T-Employee-00001" # from test records
|
||||
|
||||
jc1.append("time_logs", {
|
||||
"from_time": "2021-01-01 00:00:00",
|
||||
"to_time": "2021-01-01 08:00:00",
|
||||
"completed_qty": 1,
|
||||
"employee": employee,
|
||||
})
|
||||
jc1.save()
|
||||
|
||||
# add a new entry in same time slice
|
||||
jc2.append("time_logs", {
|
||||
"from_time": "2021-01-01 00:01:00",
|
||||
"to_time": "2021-01-01 06:00:00",
|
||||
"completed_qty": 1,
|
||||
"employee": employee,
|
||||
})
|
||||
self.assertRaises(OverlapError, jc2.save)
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
"customer",
|
||||
"warehouse",
|
||||
"project",
|
||||
"sales_order_status",
|
||||
"column_break2",
|
||||
"from_date",
|
||||
"to_date",
|
||||
"sales_order_status",
|
||||
"from_delivery_date",
|
||||
"to_delivery_date",
|
||||
"sales_orders_detail",
|
||||
"get_sales_orders",
|
||||
"sales_orders",
|
||||
@@ -358,13 +360,23 @@
|
||||
"fieldname": "get_sub_assembly_items",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Sub Assembly Items"
|
||||
},
|
||||
{
|
||||
"fieldname": "from_delivery_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "From Delivery Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "to_delivery_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "To Delivery Date"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-calendar",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-23 17:26:03.799876",
|
||||
"modified": "2021-09-06 18:35:59.642232",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Production Plan",
|
||||
|
||||
@@ -735,43 +735,42 @@ def get_material_request_items(row, sales_order, company,
|
||||
def get_sales_orders(self):
|
||||
so_filter = item_filter = ""
|
||||
bom_item = "bom.item = so_item.item_code"
|
||||
if self.from_date:
|
||||
so_filter += " and so.transaction_date >= %(from_date)s"
|
||||
if self.to_date:
|
||||
so_filter += " and so.transaction_date <= %(to_date)s"
|
||||
if self.customer:
|
||||
so_filter += " and so.customer = %(customer)s"
|
||||
if self.project:
|
||||
so_filter += " and so.project = %(project)s"
|
||||
if self.sales_order_status:
|
||||
so_filter += "and so.status = %(sales_order_status)s"
|
||||
|
||||
date_field_mapper = {
|
||||
'from_date': ('>=', 'so.transaction_date'),
|
||||
'to_date': ('<=', 'so.transaction_date'),
|
||||
'from_delivery_date': ('>=', 'so_item.delivery_date'),
|
||||
'to_delivery_date': ('<=', 'so_item.delivery_date')
|
||||
}
|
||||
|
||||
for field, value in date_field_mapper.items():
|
||||
if self.get(field):
|
||||
so_filter += f" and {value[1]} {value[0]} %({field})s"
|
||||
|
||||
for field in ['customer', 'project', 'sales_order_status']:
|
||||
if self.get(field):
|
||||
so_field = 'status' if field == 'sales_order_status' else field
|
||||
so_filter += f" and so.{so_field} = %({field})s"
|
||||
|
||||
if self.item_code and frappe.db.exists('Item', self.item_code):
|
||||
bom_item = self.get_bom_item() or bom_item
|
||||
item_filter += " and so_item.item_code = %(item)s"
|
||||
item_filter += " and so_item.item_code = %(item_code)s"
|
||||
|
||||
open_so = frappe.db.sql("""
|
||||
open_so = frappe.db.sql(f"""
|
||||
select distinct so.name, so.transaction_date, so.customer, so.base_grand_total
|
||||
from `tabSales Order` so, `tabSales Order Item` so_item
|
||||
where so_item.parent = so.name
|
||||
and so.docstatus = 1 and so.status not in ("Stopped", "Closed")
|
||||
and so.company = %(company)s
|
||||
and so_item.qty > so_item.work_order_qty {0} {1}
|
||||
and (exists (select name from `tabBOM` bom where {2}
|
||||
and so_item.qty > so_item.work_order_qty {so_filter} {item_filter}
|
||||
and (exists (select name from `tabBOM` bom where {bom_item}
|
||||
and bom.is_active = 1)
|
||||
or exists (select name from `tabPacked Item` pi
|
||||
where pi.parent = so.name and pi.parent_item = so_item.item_code
|
||||
and exists (select name from `tabBOM` bom where bom.item=pi.item_code
|
||||
and bom.is_active = 1)))
|
||||
""".format(so_filter, item_filter, bom_item), {
|
||||
"from_date": self.from_date,
|
||||
"to_date": self.to_date,
|
||||
"customer": self.customer,
|
||||
"project": self.project,
|
||||
"item": self.item_code,
|
||||
"company": self.company,
|
||||
"sales_order_status": self.sales_order_status
|
||||
}, as_dict=1)
|
||||
""", self.as_dict(), as_dict=1)
|
||||
|
||||
return open_so
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -306,6 +306,7 @@ erpnext.patches.v13_0.add_custom_field_for_south_africa #2
|
||||
erpnext.patches.v13_0.rename_discharge_ordered_date_in_ip_record
|
||||
erpnext.patches.v13_0.migrate_stripe_api
|
||||
erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries
|
||||
erpnext.patches.v13_0.custom_fields_for_taxjar_integration
|
||||
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_website_items
|
||||
|
||||
@@ -7,6 +7,8 @@ def execute():
|
||||
frappe.reload_doc("e_commerce", "doctype", "website_item")
|
||||
frappe.reload_doc("e_commerce", "doctype", "website_item_tabbed_section")
|
||||
frappe.reload_doc("e_commerce", "doctype", "website_offer")
|
||||
frappe.reload_doc("e_commerce", "doctype", "recommended_items")
|
||||
frappe.reload_doc("e_commerce", "doctype", "e_commerce_settings")
|
||||
frappe.reload_doc("stock", "doctype", "item")
|
||||
|
||||
item_fields = ["item_code", "item_name", "item_group", "stock_uom", "brand", "image",
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
from erpnext.regional.united_states.setup import add_permissions
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all('Company', filters = {'country': 'United States'}, fields=['name'])
|
||||
if not company:
|
||||
return
|
||||
|
||||
frappe.reload_doc("regional", "doctype", "product_tax_category")
|
||||
|
||||
custom_fields = {
|
||||
'Sales Invoice Item': [
|
||||
dict(fieldname='product_tax_category', fieldtype='Link', insert_after='description', options='Product Tax Category',
|
||||
label='Product Tax Category', fetch_from='item_code.product_tax_category'),
|
||||
dict(fieldname='tax_collectable', fieldtype='Currency', insert_after='net_amount',
|
||||
label='Tax Collectable', read_only=1),
|
||||
dict(fieldname='taxable_amount', fieldtype='Currency', insert_after='tax_collectable',
|
||||
label='Taxable Amount', read_only=1)
|
||||
],
|
||||
'Item': [
|
||||
dict(fieldname='product_tax_category', fieldtype='Link', insert_after='item_group', options='Product Tax Category',
|
||||
label='Product Tax Category')
|
||||
]
|
||||
}
|
||||
create_custom_fields(custom_fields, update=True)
|
||||
add_permissions()
|
||||
frappe.enqueue('erpnext.regional.united_states.setup.add_product_tax_categories', now=True)
|
||||
@@ -13,7 +13,7 @@ def execute():
|
||||
frappe.reload_doc('stock', 'doctype', 'stock_settings')
|
||||
|
||||
def update_from_return_docs(doctype):
|
||||
for return_doc in frappe.get_all(doctype, filters={'is_return' : 1, 'docstatus' : 1}):
|
||||
for return_doc in frappe.get_all(doctype, filters={'is_return' : 1, 'docstatus' : 1, 'return_against': ('!=', '')}):
|
||||
# Update original receipt/delivery document from return
|
||||
return_doc = frappe.get_cached_doc(doctype, return_doc.name)
|
||||
try:
|
||||
|
||||
@@ -14,12 +14,11 @@ from erpnext.hr.utils import validate_active_employee
|
||||
|
||||
class AdditionalSalary(Document):
|
||||
def on_submit(self):
|
||||
if self.ref_doctype == "Employee Advance" and self.ref_docname:
|
||||
frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount)
|
||||
|
||||
self.update_return_amount_in_employee_advance()
|
||||
self.update_employee_referral()
|
||||
|
||||
def on_cancel(self):
|
||||
self.update_return_amount_in_employee_advance()
|
||||
self.update_employee_referral(cancel=True)
|
||||
|
||||
def validate(self):
|
||||
@@ -98,6 +97,17 @@ class AdditionalSalary(Document):
|
||||
frappe.throw(_("Additional Salary for referral bonus can only be created against Employee Referral with status {0}").format(
|
||||
frappe.bold("Accepted")))
|
||||
|
||||
def update_return_amount_in_employee_advance(self):
|
||||
if self.ref_doctype == "Employee Advance" and self.ref_docname:
|
||||
return_amount = frappe.db.get_value("Employee Advance", self.ref_docname, "return_amount")
|
||||
|
||||
if self.docstatus == 2:
|
||||
return_amount -= self.amount
|
||||
else:
|
||||
return_amount += self.amount
|
||||
|
||||
frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", return_amount)
|
||||
|
||||
def update_employee_referral(self, cancel=False):
|
||||
if self.ref_doctype == "Employee Referral":
|
||||
status = "Unpaid" if cancel else "Paid"
|
||||
|
||||
@@ -4,18 +4,11 @@
|
||||
frappe.ui.form.on('Salary Component', {
|
||||
setup: function(frm) {
|
||||
frm.set_query("account", "accounts", function(doc, cdt, cdn) {
|
||||
let d = frappe.get_doc(cdt, cdn);
|
||||
|
||||
let root_type = "Liability";
|
||||
if (frm.doc.type == "Deduction") {
|
||||
root_type = "Expense";
|
||||
}
|
||||
|
||||
var d = locals[cdt][cdn];
|
||||
return {
|
||||
filters: {
|
||||
"is_group": 0,
|
||||
"company": d.company,
|
||||
"root_type": root_type
|
||||
"company": d.company
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -487,7 +487,7 @@ class SalarySlip(TransactionBase):
|
||||
self.calculate_component_amounts("deductions")
|
||||
|
||||
self.set_loan_repayment()
|
||||
self.set_component_amounts_based_on_payment_days()
|
||||
self.set_precision_for_component_amounts()
|
||||
self.set_net_pay()
|
||||
|
||||
def set_net_pay(self):
|
||||
@@ -713,6 +713,17 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
component_row.amount = amount
|
||||
|
||||
self.update_component_amount_based_on_payment_days(component_row)
|
||||
|
||||
def update_component_amount_based_on_payment_days(self, component_row):
|
||||
joining_date, relieving_date = self.get_joining_and_relieving_dates()
|
||||
component_row.amount = self.get_amount_based_on_payment_days(component_row, joining_date, relieving_date)[0]
|
||||
|
||||
def set_precision_for_component_amounts(self):
|
||||
for component_type in ("earnings", "deductions"):
|
||||
for component_row in self.get(component_type):
|
||||
component_row.amount = flt(component_row.amount, component_row.precision("amount"))
|
||||
|
||||
def calculate_variable_based_on_taxable_salary(self, tax_component, payroll_period):
|
||||
if not payroll_period:
|
||||
frappe.msgprint(_("Start and end dates not in a valid Payroll Period, cannot calculate {0}.")
|
||||
@@ -870,14 +881,7 @@ class SalarySlip(TransactionBase):
|
||||
return total_tax_paid
|
||||
|
||||
def get_taxable_earnings(self, allow_tax_exemption=False, based_on_payment_days=0):
|
||||
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
|
||||
["date_of_joining", "relieving_date"])
|
||||
|
||||
if not relieving_date:
|
||||
relieving_date = getdate(self.end_date)
|
||||
|
||||
if not joining_date:
|
||||
frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name)))
|
||||
joining_date, relieving_date = self.get_joining_and_relieving_dates()
|
||||
|
||||
taxable_earnings = 0
|
||||
additional_income = 0
|
||||
@@ -888,7 +892,10 @@ class SalarySlip(TransactionBase):
|
||||
if based_on_payment_days:
|
||||
amount, additional_amount = self.get_amount_based_on_payment_days(earning, joining_date, relieving_date)
|
||||
else:
|
||||
amount, additional_amount = earning.amount, earning.additional_amount
|
||||
if earning.additional_amount:
|
||||
amount, additional_amount = earning.amount, earning.additional_amount
|
||||
else:
|
||||
amount, additional_amount = earning.default_amount, earning.additional_amount
|
||||
|
||||
if earning.is_tax_applicable:
|
||||
if additional_amount:
|
||||
@@ -1059,7 +1066,7 @@ class SalarySlip(TransactionBase):
|
||||
total += amount
|
||||
return total
|
||||
|
||||
def set_component_amounts_based_on_payment_days(self):
|
||||
def get_joining_and_relieving_dates(self):
|
||||
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
|
||||
["date_of_joining", "relieving_date"])
|
||||
|
||||
@@ -1069,9 +1076,7 @@ class SalarySlip(TransactionBase):
|
||||
if not joining_date:
|
||||
frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name)))
|
||||
|
||||
for component_type in ("earnings", "deductions"):
|
||||
for d in self.get(component_type):
|
||||
d.amount = flt(self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0], d.precision("amount"))
|
||||
return joining_date, relieving_date
|
||||
|
||||
def set_loan_repayment(self):
|
||||
self.total_loan_repayment = 0
|
||||
|
||||
@@ -17,6 +17,7 @@ from frappe.utils import (
|
||||
getdate,
|
||||
nowdate,
|
||||
)
|
||||
from frappe.utils.make_random import get_random
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
@@ -134,6 +135,65 @@ class TestSalarySlip(unittest.TestCase):
|
||||
|
||||
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
|
||||
|
||||
def test_component_amount_dependent_on_another_payment_days_based_component(self):
|
||||
from erpnext.hr.doctype.attendance.attendance import mark_attendance
|
||||
from erpnext.payroll.doctype.salary_structure.test_salary_structure import (
|
||||
create_salary_structure_assignment,
|
||||
)
|
||||
|
||||
no_of_days = self.get_no_of_days()
|
||||
# Payroll based on attendance
|
||||
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance")
|
||||
|
||||
salary_structure = make_salary_structure_for_payment_days_based_component_dependency()
|
||||
employee = make_employee("test_payment_days_based_component@salary.com", company="_Test Company")
|
||||
|
||||
# base = 50000
|
||||
create_salary_structure_assignment(employee, salary_structure.name, company="_Test Company", currency="INR")
|
||||
|
||||
# mark employee absent for a day since this case works fine if payment days are equal to working days
|
||||
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(employee, add_days(first_sunday, 1), 'Absent', ignore_validate=True) # counted as absent
|
||||
|
||||
# make salary slip and assert payment days
|
||||
ss = make_salary_slip_for_payment_days_dependency_test("test_payment_days_based_component@salary.com", salary_structure.name)
|
||||
self.assertEqual(ss.absent_days, 1)
|
||||
|
||||
days_in_month = no_of_days[0]
|
||||
no_of_holidays = no_of_days[1]
|
||||
|
||||
self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 1)
|
||||
|
||||
ss.reload()
|
||||
payment_days_based_comp_amount = 0
|
||||
for component in ss.earnings:
|
||||
if component.salary_component == "HRA - Payment Days":
|
||||
payment_days_based_comp_amount = flt(component.amount, component.precision("amount"))
|
||||
break
|
||||
|
||||
# check if the dependent component is calculated using the amount updated after payment days
|
||||
actual_amount = 0
|
||||
precision = 0
|
||||
for component in ss.deductions:
|
||||
if component.salary_component == "P - Employee Provident Fund":
|
||||
precision = component.precision("amount")
|
||||
actual_amount = flt(component.amount, precision)
|
||||
break
|
||||
|
||||
expected_amount = flt((flt(ss.gross_pay) - payment_days_based_comp_amount) * 0.12, precision)
|
||||
|
||||
self.assertEqual(actual_amount, expected_amount)
|
||||
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
|
||||
|
||||
def test_salary_slip_with_holidays_included(self):
|
||||
no_of_days = self.get_no_of_days()
|
||||
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1)
|
||||
@@ -851,6 +911,7 @@ def setup_test():
|
||||
|
||||
def make_holiday_list():
|
||||
fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company())
|
||||
holiday_list = frappe.db.exists("Holiday List", "Salary Slip Test Holiday List")
|
||||
if not frappe.db.get_value("Holiday List", "Salary Slip Test Holiday List"):
|
||||
holiday_list = frappe.get_doc({
|
||||
"doctype": "Holiday List",
|
||||
@@ -861,3 +922,94 @@ def make_holiday_list():
|
||||
}).insert()
|
||||
holiday_list.get_weekly_off_dates()
|
||||
holiday_list.save()
|
||||
holiday_list = holiday_list.name
|
||||
|
||||
return holiday_list
|
||||
|
||||
def make_salary_structure_for_payment_days_based_component_dependency():
|
||||
earnings = [
|
||||
{
|
||||
"salary_component": "Basic Salary - Payment Days",
|
||||
"abbr": "P_BS",
|
||||
"type": "Earning",
|
||||
"formula": "base",
|
||||
"amount_based_on_formula": 1
|
||||
},
|
||||
{
|
||||
"salary_component": "HRA - Payment Days",
|
||||
"abbr": "P_HRA",
|
||||
"type": "Earning",
|
||||
"depends_on_payment_days": 1,
|
||||
"amount_based_on_formula": 1,
|
||||
"formula": "base * 0.20"
|
||||
}
|
||||
]
|
||||
|
||||
make_salary_component(earnings, False, company_list=["_Test Company"])
|
||||
|
||||
deductions = [
|
||||
{
|
||||
"salary_component": "P - Professional Tax",
|
||||
"abbr": "P_PT",
|
||||
"type": "Deduction",
|
||||
"depends_on_payment_days": 1,
|
||||
"amount": 200.00
|
||||
},
|
||||
{
|
||||
"salary_component": "P - Employee Provident Fund",
|
||||
"abbr": "P_EPF",
|
||||
"type": "Deduction",
|
||||
"exempted_from_income_tax": 1,
|
||||
"amount_based_on_formula": 1,
|
||||
"depends_on_payment_days": 0,
|
||||
"formula": "(gross_pay - P_HRA) * 0.12"
|
||||
}
|
||||
]
|
||||
|
||||
make_salary_component(deductions, False, company_list=["_Test Company"])
|
||||
|
||||
salary_structure = "Salary Structure with PF"
|
||||
if frappe.db.exists("Salary Structure", salary_structure):
|
||||
frappe.db.delete("Salary Structure", salary_structure)
|
||||
|
||||
details = {
|
||||
"doctype": "Salary Structure",
|
||||
"name": salary_structure,
|
||||
"company": "_Test Company",
|
||||
"payroll_frequency": "Monthly",
|
||||
"payment_account": get_random("Account", filters={"account_currency": "INR"}),
|
||||
"currency": "INR"
|
||||
}
|
||||
|
||||
salary_structure_doc = frappe.get_doc(details)
|
||||
|
||||
for entry in earnings:
|
||||
salary_structure_doc.append("earnings", entry)
|
||||
|
||||
for entry in deductions:
|
||||
salary_structure_doc.append("deductions", entry)
|
||||
|
||||
salary_structure_doc.insert()
|
||||
salary_structure_doc.submit()
|
||||
|
||||
return salary_structure_doc
|
||||
|
||||
def make_salary_slip_for_payment_days_dependency_test(employee, salary_structure):
|
||||
employee = frappe.db.get_value("Employee", {
|
||||
"user_id": employee
|
||||
},
|
||||
["name", "company", "employee_name"],
|
||||
as_dict=True)
|
||||
|
||||
salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": employee})})
|
||||
|
||||
if not salary_slip_name:
|
||||
salary_slip = make_salary_slip(salary_structure, employee=employee.name)
|
||||
salary_slip.employee_name = employee.employee_name
|
||||
salary_slip.payroll_frequency = "Monthly"
|
||||
salary_slip.posting_date = nowdate()
|
||||
salary_slip.insert()
|
||||
else:
|
||||
salary_slip = frappe.get_doc("Salary Slip", salary_slip_name)
|
||||
|
||||
return salary_slip
|
||||
@@ -227,7 +227,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "no_matching_vouchers",
|
||||
options: "<div class='text-muted text-center'>No Matching Vouchers Found</div>"
|
||||
options: "<div class=\"text-muted text-center\">No Matching Vouchers Found</div>"
|
||||
},
|
||||
{
|
||||
fieldtype: "Section Break",
|
||||
|
||||
@@ -709,6 +709,9 @@ erpnext.utils.map_current_doc = function(opts) {
|
||||
setters: opts.setters,
|
||||
get_query: opts.get_query,
|
||||
add_filters_group: 1,
|
||||
allow_child_item_selection: opts.allow_child_item_selection,
|
||||
child_fieldname: opts.child_fielname,
|
||||
child_columns: opts.child_columns,
|
||||
action: function(selections, args) {
|
||||
let values = selections;
|
||||
if(values.length === 0){
|
||||
@@ -716,7 +719,7 @@ erpnext.utils.map_current_doc = function(opts) {
|
||||
return;
|
||||
}
|
||||
opts.source_name = values;
|
||||
opts.setters = args;
|
||||
opts.args = args;
|
||||
d.dialog.hide();
|
||||
_map();
|
||||
},
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Product Tax Category', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "field:product_tax_code",
|
||||
"creation": "2021-08-23 12:33:37.910225",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"product_tax_code",
|
||||
"column_break_2",
|
||||
"category_name",
|
||||
"section_break_4",
|
||||
"description"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "product_tax_code",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Product Tax Code",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "category_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Category Name",
|
||||
"length": 255
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-24 09:10:25.313642",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Regional",
|
||||
"name": "Product Tax Category",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "category_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class ProductTaxCategory(Document):
|
||||
pass
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
|
||||
class TestProductTaxCategory(unittest.TestCase):
|
||||
pass
|
||||
4084
erpnext/regional/united_states/product_tax_category_data.json
Normal file
4084
erpnext/regional/united_states/product_tax_category_data.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,13 +4,42 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import os
|
||||
import json
|
||||
from frappe.permissions import add_permission, update_permission_property
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def setup(company=None, patch=True):
|
||||
# Company independent fixtures should be called only once at the first company setup
|
||||
if frappe.db.count('Company', {'country': 'United States'}) <=1:
|
||||
setup_company_independent_fixtures(patch=patch)
|
||||
|
||||
def setup_company_independent_fixtures(company=None, patch=True):
|
||||
add_product_tax_categories()
|
||||
make_custom_fields()
|
||||
add_permissions()
|
||||
frappe.enqueue('erpnext.regional.united_states.setup.add_product_tax_categories', now=False)
|
||||
add_print_formats()
|
||||
|
||||
# Product Tax categories imported from taxjar api
|
||||
def add_product_tax_categories():
|
||||
with open(os.path.join(os.path.dirname(__file__), 'product_tax_category_data.json'), 'r') as f:
|
||||
tax_categories = json.loads(f.read())
|
||||
create_tax_categories(tax_categories['categories'])
|
||||
|
||||
def create_tax_categories(data):
|
||||
for d in data:
|
||||
tax_category = frappe.new_doc('Product Tax Category')
|
||||
tax_category.description = d.get("description")
|
||||
tax_category.product_tax_code = d.get("product_tax_code")
|
||||
tax_category.category_name = d.get("name")
|
||||
try:
|
||||
tax_category.db_insert()
|
||||
except frappe.DuplicateEntryError:
|
||||
pass
|
||||
|
||||
|
||||
def make_custom_fields(update=True):
|
||||
custom_fields = {
|
||||
'Supplier': [
|
||||
@@ -32,10 +61,29 @@ def make_custom_fields(update=True):
|
||||
'Quotation': [
|
||||
dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='taxes_and_charges',
|
||||
label='Is customer exempted from sales tax?')
|
||||
],
|
||||
'Sales Invoice Item': [
|
||||
dict(fieldname='product_tax_category', fieldtype='Link', insert_after='description', options='Product Tax Category',
|
||||
label='Product Tax Category', fetch_from='item_code.product_tax_category'),
|
||||
dict(fieldname='tax_collectable', fieldtype='Currency', insert_after='net_amount',
|
||||
label='Tax Collectable', read_only=1),
|
||||
dict(fieldname='taxable_amount', fieldtype='Currency', insert_after='tax_collectable',
|
||||
label='Taxable Amount', read_only=1)
|
||||
],
|
||||
'Item': [
|
||||
dict(fieldname='product_tax_category', fieldtype='Link', insert_after='item_group', options='Product Tax Category',
|
||||
label='Product Tax Category')
|
||||
]
|
||||
}
|
||||
create_custom_fields(custom_fields, update=update)
|
||||
|
||||
def add_permissions():
|
||||
doctype = "Product Tax Category"
|
||||
for role in ('Accounts Manager', 'Accounts User', 'System Manager','Item Manager', 'Stock Manager'):
|
||||
add_permission(doctype, role, 0)
|
||||
update_permission_property(doctype, role, 0, 'write', 1)
|
||||
update_permission_property(doctype, role, 0, 'create', 1)
|
||||
|
||||
def add_print_formats():
|
||||
frappe.reload_doc("regional", "print_format", "irs_1099_form")
|
||||
frappe.db.set_value("Print Format", "IRS 1099 Form", "disabled", 0)
|
||||
|
||||
@@ -297,6 +297,7 @@ erpnext.PointOfSale.Payment = class {
|
||||
this.render_payment_mode_dom();
|
||||
this.make_invoice_fields_control();
|
||||
this.update_totals_section();
|
||||
this.focus_on_default_mop();
|
||||
}
|
||||
|
||||
edit_cart() {
|
||||
@@ -378,17 +379,24 @@ erpnext.PointOfSale.Payment = class {
|
||||
});
|
||||
this[`${mode}_control`].toggle_label(false);
|
||||
this[`${mode}_control`].set_value(p.amount);
|
||||
});
|
||||
|
||||
this.render_loyalty_points_payment_mode();
|
||||
|
||||
this.attach_cash_shortcuts(doc);
|
||||
}
|
||||
|
||||
focus_on_default_mop() {
|
||||
const doc = this.events.get_frm().doc;
|
||||
const payments = doc.payments;
|
||||
payments.forEach(p => {
|
||||
const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase();
|
||||
if (p.default) {
|
||||
setTimeout(() => {
|
||||
this.$payment_modes.find(`.${mode}.mode-of-payment-control`).parent().click();
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
this.render_loyalty_points_payment_mode();
|
||||
|
||||
this.attach_cash_shortcuts(doc);
|
||||
}
|
||||
|
||||
attach_cash_shortcuts(doc) {
|
||||
|
||||
@@ -243,7 +243,12 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
|
||||
var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate"));
|
||||
|
||||
if(df && editable_price_list_rate) {
|
||||
df.read_only = 0;
|
||||
const parent_field = frappe.meta.get_parentfield(this.frm.doc.doctype, this.frm.doc.doctype + " Item");
|
||||
if (!this.frm.fields_dict[parent_field]) return;
|
||||
|
||||
this.frm.fields_dict[parent_field].grid.update_docfield_property(
|
||||
'price_list_rate', 'read_only', 0
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -950,7 +950,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"max_attachments": 1,
|
||||
"modified": "2021-08-26 12:23:07.277077",
|
||||
"modified": "2021-09-10 12:23:07.277077",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
|
||||
@@ -2,19 +2,32 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Item Variant Settings', {
|
||||
setup: function(frm) {
|
||||
refresh: function(frm) {
|
||||
const allow_fields = [];
|
||||
const exclude_fields = ["naming_series", "item_code", "item_name", "published_in_website",
|
||||
"opening_stock", "variant_of", "valuation_rate"];
|
||||
|
||||
const existing_fields = frm.doc.fields.map(row => row.field_name);
|
||||
const exclude_fields = [...existing_fields, "naming_series", "item_code", "item_name",
|
||||
"show_in_website", "show_variant_in_website", "standard_rate", "opening_stock", "image",
|
||||
"variant_of", "valuation_rate", "barcodes", "website_image", "thumbnail",
|
||||
"website_specifiations", "web_long_description", "has_variants", "attributes"];
|
||||
|
||||
const exclude_field_types = ['HTML', 'Section Break', 'Column Break', 'Button', 'Read Only'];
|
||||
|
||||
frappe.model.with_doctype('Item', () => {
|
||||
frappe.get_meta('Item').fields.forEach(d => {
|
||||
if(!in_list(['HTML', 'Section Break', 'Column Break', 'Button', 'Read Only'], d.fieldtype)
|
||||
if (!in_list(exclude_field_types, d.fieldtype)
|
||||
&& !d.no_copy && !in_list(exclude_fields, d.fieldname)) {
|
||||
allow_fields.push(d.fieldname);
|
||||
}
|
||||
});
|
||||
|
||||
if (allow_fields.length == 0) {
|
||||
allow_fields.push({
|
||||
label: __("No additional fields available"),
|
||||
value: "",
|
||||
});
|
||||
}
|
||||
|
||||
frm.fields_dict.fields.grid.update_docfield_property(
|
||||
'field_name', 'options', allow_fields
|
||||
);
|
||||
|
||||
@@ -6,10 +6,13 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.utils import cstr, flt, get_link_to_form, getdate, new_line_sep, nowdate
|
||||
from six import string_types
|
||||
|
||||
from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items
|
||||
from erpnext.controllers.buying_controller import BuyingController
|
||||
@@ -269,7 +272,10 @@ def update_status(name, status):
|
||||
material_request.update_status(status)
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_order(source_name, target_doc=None):
|
||||
def make_purchase_order(source_name, target_doc=None, args={}):
|
||||
|
||||
if isinstance(args, string_types):
|
||||
args = json.loads(args)
|
||||
|
||||
def postprocess(source, target_doc):
|
||||
if frappe.flags.args and frappe.flags.args.default_supplier:
|
||||
@@ -284,7 +290,10 @@ def make_purchase_order(source_name, target_doc=None):
|
||||
set_missing_values(source, target_doc)
|
||||
|
||||
def select_item(d):
|
||||
return d.ordered_qty < d.stock_qty
|
||||
filtered_items = args.get('filtered_children', [])
|
||||
child_filter = d.name in filtered_items if filtered_items else True
|
||||
|
||||
return d.ordered_qty < d.stock_qty and child_filter
|
||||
|
||||
doclist = get_mapped_doc("Material Request", source_name, {
|
||||
"Material Request": {
|
||||
|
||||
@@ -22,7 +22,15 @@ frappe.query_reports["Stock Ageing"] = {
|
||||
"fieldname":"warehouse",
|
||||
"label": __("Warehouse"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Warehouse"
|
||||
"options": "Warehouse",
|
||||
get_query: () => {
|
||||
const company = frappe.query_report.get_filter_value("company");
|
||||
return {
|
||||
filters: {
|
||||
...company && {company},
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"item_code",
|
||||
|
||||
@@ -53,13 +53,14 @@ frappe.query_reports["Stock Balance"] = {
|
||||
"width": "80",
|
||||
"options": "Warehouse",
|
||||
get_query: () => {
|
||||
var warehouse_type = frappe.query_report.get_filter_value('warehouse_type');
|
||||
if(warehouse_type){
|
||||
return {
|
||||
filters: {
|
||||
'warehouse_type': warehouse_type
|
||||
}
|
||||
};
|
||||
let warehouse_type = frappe.query_report.get_filter_value("warehouse_type");
|
||||
let company = frappe.query_report.get_filter_value("company");
|
||||
|
||||
return {
|
||||
filters: {
|
||||
...warehouse_type && {warehouse_type},
|
||||
...company && {company}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -673,11 +673,15 @@ class update_entries_after(object):
|
||||
if self.wh_data.stock_queue[-1][1]==incoming_rate:
|
||||
self.wh_data.stock_queue[-1][0] += actual_qty
|
||||
else:
|
||||
# Item has a positive balance qty, add new entry
|
||||
if self.wh_data.stock_queue[-1][0] > 0:
|
||||
self.wh_data.stock_queue.append([actual_qty, incoming_rate])
|
||||
else:
|
||||
else: # negative balance qty
|
||||
qty = self.wh_data.stock_queue[-1][0] + actual_qty
|
||||
self.wh_data.stock_queue[-1] = [qty, incoming_rate]
|
||||
if qty > 0: # new balance qty is positive
|
||||
self.wh_data.stock_queue[-1] = [qty, incoming_rate]
|
||||
else: # new balance qty is still negative, maintain same rate
|
||||
self.wh_data.stock_queue[-1][0] = qty
|
||||
else:
|
||||
qty_to_pop = abs(actual_qty)
|
||||
while qty_to_pop:
|
||||
|
||||
14
package.json
14
package.json
@@ -11,16 +11,10 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/frappe/erpnext/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"snyk": "^1.518.0"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"dependencies": {
|
||||
"onscan.js": "^1.5.2",
|
||||
"html2canvas": "^1.1.4"
|
||||
"html2canvas": "^1.1.4",
|
||||
"onscan.js": "^1.5.2"
|
||||
},
|
||||
"scripts": {
|
||||
"snyk-protect": "snyk protect",
|
||||
"prepare": "yarn run snyk-protect"
|
||||
},
|
||||
"snyk": true
|
||||
"scripts": {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user