mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-03 05:28:27 +00:00
Merge branch 'version-13-hotfix' into backport-fix-selling-settings
This commit is contained in:
13
.github/helper/semgrep_rules/report.yml
vendored
13
.github/helper/semgrep_rules/report.yml
vendored
@@ -19,3 +19,16 @@ rules:
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-translated-values-in-business-logic
|
||||
paths:
|
||||
include:
|
||||
- "**/report"
|
||||
patterns:
|
||||
- pattern-inside: |
|
||||
{..., filters: [...], ...}
|
||||
- pattern: |
|
||||
{..., options: [..., __("..."), ...], ...}
|
||||
message: |
|
||||
Using translated values in options field will require you to translate the values while comparing in business logic. Instead of passing translated labels provide objects that contain both label and value. e.g. { label: __("Option value"), value: "Option value"}
|
||||
languages: [javascript]
|
||||
severity: ERROR
|
||||
|
||||
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'
|
||||
@@ -13,7 +13,7 @@ def get_data():
|
||||
},
|
||||
{
|
||||
'label': _('References'),
|
||||
'items': ['Period Closing Voucher', 'Tax Withholding Category']
|
||||
'items': ['Period Closing Voucher']
|
||||
},
|
||||
{
|
||||
'label': _('Target Details'),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1128,10 +1128,11 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
tax_withholding_category = 'TDS - 194 - Dividends - Individual')
|
||||
|
||||
# Update tax withholding category with current fiscal year and rate details
|
||||
update_tax_witholding_category('_Test Company', 'TDS Payable - _TC', nowdate())
|
||||
update_tax_witholding_category('_Test Company', 'TDS Payable - _TC')
|
||||
|
||||
# Create Purchase Order with TDS applied
|
||||
po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item')
|
||||
po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item',
|
||||
posting_date='2021-09-15')
|
||||
po.apply_tds = 1
|
||||
po.tax_withholding_category = 'TDS - 194 - Dividends - Individual'
|
||||
po.save()
|
||||
@@ -1203,16 +1204,20 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
||||
doc.assertEqual(expected_gle[i][2], gle.credit)
|
||||
doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
|
||||
|
||||
def update_tax_witholding_category(company, account, date):
|
||||
def update_tax_witholding_category(company, account):
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
fiscal_year = get_fiscal_year(date=date, company=company)
|
||||
fiscal_year = get_fiscal_year(fiscal_year='_Test Fiscal Year 2021')
|
||||
|
||||
if not frappe.db.get_value('Tax Withholding Rate',
|
||||
{'parent': 'TDS - 194 - Dividends - Individual', 'fiscal_year': fiscal_year[0]}):
|
||||
{'parent': 'TDS - 194 - Dividends - Individual', 'from_date': ('>=', fiscal_year[1]),
|
||||
'to_date': ('<=', fiscal_year[2])}):
|
||||
tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual')
|
||||
tds_category.set('rates', [])
|
||||
|
||||
tds_category.append('rates', {
|
||||
'fiscal_year': fiscal_year[0],
|
||||
'from_date': fiscal_year[1],
|
||||
'to_date': fiscal_year[2],
|
||||
'tax_withholding_rate': 10,
|
||||
'single_threshold': 2500,
|
||||
'cumulative_threshold': 0
|
||||
|
||||
@@ -696,7 +696,6 @@
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Scan Barcode",
|
||||
"length": 1,
|
||||
"options": "Barcode"
|
||||
},
|
||||
{
|
||||
@@ -2033,7 +2032,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2021-08-27 20:13:40.456462",
|
||||
"modified": "2021-09-08 15:24:25.486499",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -396,6 +396,7 @@ class Subscription(Document):
|
||||
invoice.to_date = self.current_invoice_end
|
||||
|
||||
invoice.flags.ignore_mandatory = True
|
||||
invoice.set_missing_values()
|
||||
invoice.save()
|
||||
|
||||
if self.submit_invoice:
|
||||
|
||||
@@ -9,11 +9,26 @@ from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, getdate
|
||||
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
|
||||
class TaxWithholdingCategory(Document):
|
||||
pass
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
self.validate_thresholds()
|
||||
|
||||
def validate_dates(self):
|
||||
last_date = None
|
||||
for d in self.get('rates'):
|
||||
if getdate(d.from_date) >= getdate(d.to_date):
|
||||
frappe.throw(_("Row #{0}: From Date cannot be before To Date").format(d.idx))
|
||||
|
||||
# validate overlapping of dates
|
||||
if last_date and getdate(d.to_date) < getdate(last_date):
|
||||
frappe.throw(_("Row #{0}: Dates overlapping with other row").format(d.idx))
|
||||
|
||||
def validate_thresholds(self):
|
||||
for d in self.get('rates'):
|
||||
if d.cumulative_threshold and d.cumulative_threshold < d.single_threshold:
|
||||
frappe.throw(_("Row #{0}: Cumulative threshold cannot be less than Single Transaction threshold").format(d.idx))
|
||||
|
||||
def get_party_details(inv):
|
||||
party_type, party = '', ''
|
||||
@@ -52,8 +67,8 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
||||
if not parties:
|
||||
parties.append(party)
|
||||
|
||||
fiscal_year = get_fiscal_year(inv.get('posting_date') or inv.get('transaction_date'), company=inv.company)
|
||||
tax_details = get_tax_withholding_details(tax_withholding_category, fiscal_year[0], inv.company)
|
||||
posting_date = inv.get('posting_date') or inv.get('transaction_date')
|
||||
tax_details = get_tax_withholding_details(tax_withholding_category, posting_date, inv.company)
|
||||
|
||||
if not tax_details:
|
||||
frappe.throw(_('Please set associated account in Tax Withholding Category {0} against Company {1}')
|
||||
@@ -67,7 +82,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
||||
tax_amount, tax_deducted = get_tax_amount(
|
||||
party_type, parties,
|
||||
inv, tax_details,
|
||||
fiscal_year, pan_no
|
||||
posting_date, pan_no
|
||||
)
|
||||
|
||||
if party_type == 'Supplier':
|
||||
@@ -77,16 +92,18 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
||||
|
||||
return tax_row
|
||||
|
||||
def get_tax_withholding_details(tax_withholding_category, fiscal_year, company):
|
||||
def get_tax_withholding_details(tax_withholding_category, posting_date, company):
|
||||
tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category)
|
||||
|
||||
tax_rate_detail = get_tax_withholding_rates(tax_withholding, fiscal_year)
|
||||
tax_rate_detail = get_tax_withholding_rates(tax_withholding, posting_date)
|
||||
|
||||
for account_detail in tax_withholding.accounts:
|
||||
if company == account_detail.company:
|
||||
return frappe._dict({
|
||||
"account_head": account_detail.account,
|
||||
"rate": tax_rate_detail.tax_withholding_rate,
|
||||
"from_date": tax_rate_detail.from_date,
|
||||
"to_date": tax_rate_detail.to_date,
|
||||
"threshold": tax_rate_detail.single_threshold,
|
||||
"cumulative_threshold": tax_rate_detail.cumulative_threshold,
|
||||
"description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category,
|
||||
@@ -95,13 +112,13 @@ def get_tax_withholding_details(tax_withholding_category, fiscal_year, company):
|
||||
"round_off_tax_amount": tax_withholding.round_off_tax_amount
|
||||
})
|
||||
|
||||
def get_tax_withholding_rates(tax_withholding, fiscal_year):
|
||||
def get_tax_withholding_rates(tax_withholding, posting_date):
|
||||
# returns the row that matches with the fiscal year from posting date
|
||||
for rate in tax_withholding.rates:
|
||||
if rate.fiscal_year == fiscal_year:
|
||||
if getdate(rate.from_date) <= getdate(posting_date) <= getdate(rate.to_date):
|
||||
return rate
|
||||
|
||||
frappe.throw(_("No Tax Withholding data found for the current Fiscal Year."))
|
||||
frappe.throw(_("No Tax Withholding data found for the current posting date."))
|
||||
|
||||
def get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted):
|
||||
row = {
|
||||
@@ -143,38 +160,38 @@ def get_tax_row_for_tds(tax_details, tax_amount):
|
||||
"account_head": tax_details.account_head
|
||||
}
|
||||
|
||||
def get_lower_deduction_certificate(fiscal_year, pan_no):
|
||||
ldc_name = frappe.db.get_value('Lower Deduction Certificate', { 'pan_no': pan_no, 'fiscal_year': fiscal_year }, 'name')
|
||||
def get_lower_deduction_certificate(tax_details, pan_no):
|
||||
ldc_name = frappe.db.get_value('Lower Deduction Certificate',
|
||||
{
|
||||
'pan_no': pan_no,
|
||||
'valid_from': ('>=', tax_details.from_date),
|
||||
'valid_upto': ('<=', tax_details.to_date)
|
||||
}, 'name')
|
||||
|
||||
if ldc_name:
|
||||
return frappe.get_doc('Lower Deduction Certificate', ldc_name)
|
||||
|
||||
def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None):
|
||||
fiscal_year = fiscal_year_details[0]
|
||||
|
||||
|
||||
vouchers = get_invoice_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
|
||||
advance_vouchers = get_advance_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
|
||||
def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=None):
|
||||
vouchers = get_invoice_vouchers(parties, tax_details, inv.company, party_type=party_type)
|
||||
advance_vouchers = get_advance_vouchers(parties, company=inv.company, from_date=tax_details.from_date,
|
||||
to_date=tax_details.to_date, party_type=party_type)
|
||||
taxable_vouchers = vouchers + advance_vouchers
|
||||
|
||||
tax_deducted = 0
|
||||
if taxable_vouchers:
|
||||
tax_deducted = get_deducted_tax(taxable_vouchers, fiscal_year, tax_details)
|
||||
tax_deducted = get_deducted_tax(taxable_vouchers, tax_details)
|
||||
|
||||
tax_amount = 0
|
||||
posting_date = inv.get('posting_date') or inv.get('transaction_date')
|
||||
if party_type == 'Supplier':
|
||||
ldc = get_lower_deduction_certificate(fiscal_year, pan_no)
|
||||
ldc = get_lower_deduction_certificate(tax_details, pan_no)
|
||||
if tax_deducted:
|
||||
net_total = inv.net_total
|
||||
if ldc:
|
||||
tax_amount = get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, posting_date, net_total)
|
||||
tax_amount = get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total)
|
||||
else:
|
||||
tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0
|
||||
else:
|
||||
tax_amount = get_tds_amount(
|
||||
ldc, parties, inv, tax_details,
|
||||
fiscal_year_details, tax_deducted, vouchers
|
||||
)
|
||||
tax_amount = get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers)
|
||||
|
||||
elif party_type == 'Customer':
|
||||
if tax_deducted:
|
||||
@@ -183,14 +200,11 @@ def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, p
|
||||
else:
|
||||
# if no TCS has been charged in FY,
|
||||
# then chargeable value is "prev invoices + advances" value which cross the threshold
|
||||
tax_amount = get_tcs_amount(
|
||||
parties, inv, tax_details,
|
||||
fiscal_year_details, vouchers, advance_vouchers
|
||||
)
|
||||
tax_amount = get_tcs_amount(parties, inv, tax_details, vouchers, advance_vouchers)
|
||||
|
||||
return tax_amount, tax_deducted
|
||||
|
||||
def get_invoice_vouchers(parties, fiscal_year, company, party_type='Supplier'):
|
||||
def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'):
|
||||
dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit'
|
||||
|
||||
filters = {
|
||||
@@ -198,14 +212,14 @@ def get_invoice_vouchers(parties, fiscal_year, company, party_type='Supplier'):
|
||||
'company': company,
|
||||
'party_type': party_type,
|
||||
'party': ['in', parties],
|
||||
'fiscal_year': fiscal_year,
|
||||
'posting_date': ['between', (tax_details.from_date, tax_details.to_date)],
|
||||
'is_opening': 'No',
|
||||
'is_cancelled': 0
|
||||
}
|
||||
|
||||
return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck="voucher_no") or [""]
|
||||
|
||||
def get_advance_vouchers(parties, fiscal_year=None, company=None, from_date=None, to_date=None, party_type='Supplier'):
|
||||
def get_advance_vouchers(parties, company=None, from_date=None, to_date=None, party_type='Supplier'):
|
||||
# for advance vouchers, debit and credit is reversed
|
||||
dr_or_cr = 'debit' if party_type == 'Supplier' else 'credit'
|
||||
|
||||
@@ -218,8 +232,6 @@ def get_advance_vouchers(parties, fiscal_year=None, company=None, from_date=None
|
||||
'against_voucher': ['is', 'not set']
|
||||
}
|
||||
|
||||
if fiscal_year:
|
||||
filters['fiscal_year'] = fiscal_year
|
||||
if company:
|
||||
filters['company'] = company
|
||||
if from_date and to_date:
|
||||
@@ -227,20 +239,21 @@ def get_advance_vouchers(parties, fiscal_year=None, company=None, from_date=None
|
||||
|
||||
return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck='voucher_no') or [""]
|
||||
|
||||
def get_deducted_tax(taxable_vouchers, fiscal_year, tax_details):
|
||||
def get_deducted_tax(taxable_vouchers, tax_details):
|
||||
# check if TDS / TCS account is already charged on taxable vouchers
|
||||
filters = {
|
||||
'is_cancelled': 0,
|
||||
'credit': ['>', 0],
|
||||
'fiscal_year': fiscal_year,
|
||||
'posting_date': ['between', (tax_details.from_date, tax_details.to_date)],
|
||||
'account': tax_details.account_head,
|
||||
'voucher_no': ['in', taxable_vouchers],
|
||||
}
|
||||
field = "sum(credit)"
|
||||
field = "credit"
|
||||
|
||||
return frappe.db.get_value('GL Entry', filters, field) or 0.0
|
||||
entries = frappe.db.get_all('GL Entry', filters, pluck=field)
|
||||
return sum(entries)
|
||||
|
||||
def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers):
|
||||
def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
|
||||
tds_amount = 0
|
||||
invoice_filters = {
|
||||
'name': ('in', vouchers),
|
||||
@@ -264,7 +277,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu
|
||||
supp_credit_amt += supp_jv_credit_amt
|
||||
supp_credit_amt += inv.net_total
|
||||
|
||||
debit_note_amount = get_debit_note_amount(parties, fiscal_year_details, inv.company)
|
||||
debit_note_amount = get_debit_note_amount(parties, tax_details.from_date, tax_details.to_date, inv.company)
|
||||
supp_credit_amt -= debit_note_amount
|
||||
|
||||
threshold = tax_details.get('threshold', 0)
|
||||
@@ -292,9 +305,8 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu
|
||||
|
||||
return tds_amount
|
||||
|
||||
def get_tcs_amount(parties, inv, tax_details, fiscal_year_details, vouchers, adv_vouchers):
|
||||
def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
|
||||
tcs_amount = 0
|
||||
fiscal_year, _, _ = fiscal_year_details
|
||||
|
||||
# sum of debit entries made from sales invoices
|
||||
invoiced_amt = frappe.db.get_value('GL Entry', {
|
||||
@@ -313,14 +325,14 @@ def get_tcs_amount(parties, inv, tax_details, fiscal_year_details, vouchers, adv
|
||||
}, 'sum(credit)') or 0.0
|
||||
|
||||
# sum of credit entries made from sales invoice
|
||||
credit_note_amt = frappe.db.get_value('GL Entry', {
|
||||
credit_note_amt = sum(frappe.db.get_all('GL Entry', {
|
||||
'is_cancelled': 0,
|
||||
'credit': ['>', 0],
|
||||
'party': ['in', parties],
|
||||
'fiscal_year': fiscal_year,
|
||||
'posting_date': ['between', (tax_details.from_date, tax_details.to_date)],
|
||||
'company': inv.company,
|
||||
'voucher_type': 'Sales Invoice',
|
||||
}, 'sum(credit)') or 0.0
|
||||
}, pluck='credit'))
|
||||
|
||||
cumulative_threshold = tax_details.get('cumulative_threshold', 0)
|
||||
|
||||
@@ -339,7 +351,7 @@ def get_invoice_total_without_tcs(inv, tax_details):
|
||||
|
||||
return inv.grand_total - tcs_tax_row_amount
|
||||
|
||||
def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, posting_date, net_total):
|
||||
def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total):
|
||||
tds_amount = 0
|
||||
limit_consumed = frappe.db.get_value('Purchase Invoice', {
|
||||
'supplier': ('in', parties),
|
||||
@@ -356,14 +368,13 @@ def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, post
|
||||
|
||||
return tds_amount
|
||||
|
||||
def get_debit_note_amount(suppliers, fiscal_year_details, company=None):
|
||||
_, year_start_date, year_end_date = fiscal_year_details
|
||||
def get_debit_note_amount(suppliers, from_date, to_date, company=None):
|
||||
|
||||
filters = {
|
||||
'supplier': ['in', suppliers],
|
||||
'is_return': 1,
|
||||
'docstatus': 1,
|
||||
'posting_date': ['between', (year_start_date, year_end_date)]
|
||||
'posting_date': ['between', (from_date, to_date)]
|
||||
}
|
||||
fields = ['abs(sum(net_total)) as net_total']
|
||||
|
||||
|
||||
@@ -313,16 +313,16 @@ def create_records():
|
||||
}).insert()
|
||||
|
||||
def create_tax_with_holding_category():
|
||||
fiscal_year = get_fiscal_year(today(), company="_Test Company")[0]
|
||||
|
||||
# Cummulative thresold
|
||||
fiscal_year = get_fiscal_year(today(), company="_Test Company")
|
||||
# Cumulative threshold
|
||||
if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TDS"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Tax Withholding Category",
|
||||
"name": "Cumulative Threshold TDS",
|
||||
"category_name": "10% TDS",
|
||||
"rates": [{
|
||||
'fiscal_year': fiscal_year,
|
||||
'from_date': fiscal_year[1],
|
||||
'to_date': fiscal_year[2],
|
||||
'tax_withholding_rate': 10,
|
||||
'single_threshold': 0,
|
||||
'cumulative_threshold': 30000.00
|
||||
@@ -339,7 +339,8 @@ def create_tax_with_holding_category():
|
||||
"name": "Cumulative Threshold TCS",
|
||||
"category_name": "10% TCS",
|
||||
"rates": [{
|
||||
'fiscal_year': fiscal_year,
|
||||
'from_date': fiscal_year[1],
|
||||
'to_date': fiscal_year[2],
|
||||
'tax_withholding_rate': 10,
|
||||
'single_threshold': 0,
|
||||
'cumulative_threshold': 30000.00
|
||||
@@ -357,7 +358,8 @@ def create_tax_with_holding_category():
|
||||
"name": "Single Threshold TDS",
|
||||
"category_name": "10% TDS",
|
||||
"rates": [{
|
||||
'fiscal_year': fiscal_year,
|
||||
'from_date': fiscal_year[1],
|
||||
'to_date': fiscal_year[2],
|
||||
'tax_withholding_rate': 10,
|
||||
'single_threshold': 20000.00,
|
||||
'cumulative_threshold': 0
|
||||
@@ -377,7 +379,8 @@ def create_tax_with_holding_category():
|
||||
"consider_party_ledger_amount": 1,
|
||||
"tax_on_excess_amount": 1,
|
||||
"rates": [{
|
||||
'fiscal_year': fiscal_year,
|
||||
'from_date': fiscal_year[1],
|
||||
'to_date': fiscal_year[2],
|
||||
'tax_withholding_rate': 10,
|
||||
'single_threshold': 0,
|
||||
'cumulative_threshold': 30000
|
||||
|
||||
@@ -1,202 +1,72 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2018-07-17 16:53:13.716665",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"creation": "2018-07-17 16:53:13.716665",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"from_date",
|
||||
"to_date",
|
||||
"tax_withholding_rate",
|
||||
"column_break_3",
|
||||
"single_threshold",
|
||||
"cumulative_threshold"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "fiscal_year",
|
||||
"fieldtype": "Link",
|
||||
"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": "Fiscal Year",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Fiscal Year",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 1,
|
||||
"fieldname": "tax_withholding_rate",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Tax Withholding Rate",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "tax_withholding_rate",
|
||||
"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": "Tax Withholding Rate",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"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,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fieldname": "single_threshold",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Single Transaction Threshold"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 3,
|
||||
"fieldname": "single_threshold",
|
||||
"fieldtype": "Currency",
|
||||
"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": "Single Transaction Threshold",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 3,
|
||||
"fieldname": "cumulative_threshold",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Cumulative Transaction Threshold"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 3,
|
||||
"fieldname": "cumulative_threshold",
|
||||
"fieldtype": "Currency",
|
||||
"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": "Cumulative Transaction Threshold",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"columns": 2,
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "From Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "to_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "To Date",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-07-17 17:13:09.819580",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Withholding Rate",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-31 11:42:12.213977",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Withholding Rate",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -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":
|
||||
|
||||
@@ -260,7 +260,12 @@ def get_company_currency(filters=None):
|
||||
def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters):
|
||||
for entries in gl_entries_by_account.values():
|
||||
for entry in entries:
|
||||
d = accounts_by_name.get(entry.account_name)
|
||||
if entry.account_number:
|
||||
account_name = entry.account_number + ' - ' + entry.account_name
|
||||
else:
|
||||
account_name = entry.account_name
|
||||
|
||||
d = accounts_by_name.get(account_name)
|
||||
if d:
|
||||
for company in companies:
|
||||
# check if posting date is within the period
|
||||
@@ -307,7 +312,14 @@ def update_parent_account_names(accounts):
|
||||
of account_number and suffix of company abbr. This function adds key called
|
||||
`parent_account_name` which does not have such prefix/suffix.
|
||||
"""
|
||||
name_to_account_map = { d.name : d.account_name for d in accounts }
|
||||
name_to_account_map = {}
|
||||
|
||||
for d in accounts:
|
||||
if d.account_number:
|
||||
account_name = d.account_number + ' - ' + d.account_name
|
||||
else:
|
||||
account_name = d.account_name
|
||||
name_to_account_map[d.name] = account_name
|
||||
|
||||
for account in accounts:
|
||||
if account.parent_account:
|
||||
@@ -420,7 +432,11 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g
|
||||
convert_to_presentation_currency(gl_entries, currency_info, filters.get('company'))
|
||||
|
||||
for entry in gl_entries:
|
||||
account_name = entry.account_name
|
||||
if entry.account_number:
|
||||
account_name = entry.account_number + ' - ' + entry.account_name
|
||||
else:
|
||||
account_name = entry.account_name
|
||||
|
||||
validate_entries(account_name, entry, accounts_by_name, accounts)
|
||||
gl_entries_by_account.setdefault(account_name, []).append(entry)
|
||||
|
||||
@@ -491,7 +507,12 @@ def filter_accounts(accounts, depth=10):
|
||||
parent_children_map = {}
|
||||
accounts_by_name = {}
|
||||
for d in accounts:
|
||||
accounts_by_name[d.account_name] = d
|
||||
if d.account_number:
|
||||
account_name = d.account_number + ' - ' + d.account_name
|
||||
else:
|
||||
account_name = d.account_name
|
||||
accounts_by_name[account_name] = d
|
||||
|
||||
parent_children_map.setdefault(d.parent_account or None, []).append(d)
|
||||
|
||||
filtered_accounts = []
|
||||
|
||||
@@ -110,9 +110,26 @@ frappe.query_reports["General Ledger"] = {
|
||||
"fieldname":"group_by",
|
||||
"label": __("Group by"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["", __("Group by Voucher"), __("Group by Voucher (Consolidated)"),
|
||||
__("Group by Account"), __("Group by Party")],
|
||||
"default": __("Group by Voucher (Consolidated)")
|
||||
"options": [
|
||||
"",
|
||||
{
|
||||
label: __("Group by Voucher"),
|
||||
value: "Group by Voucher",
|
||||
},
|
||||
{
|
||||
label: __("Group by Voucher (Consolidated)"),
|
||||
value: "Group by Voucher (Consolidated)",
|
||||
},
|
||||
{
|
||||
label: __("Group by Account"),
|
||||
value: "Group by Account",
|
||||
},
|
||||
{
|
||||
label: __("Group by Party"),
|
||||
value: "Group by Party",
|
||||
},
|
||||
],
|
||||
"default": "Group by Voucher (Consolidated)"
|
||||
},
|
||||
{
|
||||
"fieldname":"tax_id",
|
||||
|
||||
@@ -62,14 +62,14 @@ def validate_filters(filters, account_details):
|
||||
if not account_details.get(account):
|
||||
frappe.throw(_("Account {0} does not exists").format(account))
|
||||
|
||||
if (filters.get("account") and filters.get("group_by") == _('Group by Account')):
|
||||
if (filters.get("account") and filters.get("group_by") == 'Group by Account'):
|
||||
filters.account = frappe.parse_json(filters.get('account'))
|
||||
for account in filters.account:
|
||||
if account_details[account].is_group == 0:
|
||||
frappe.throw(_("Can not filter based on Child Account, if grouped by Account"))
|
||||
|
||||
if (filters.get("voucher_no")
|
||||
and filters.get("group_by") in [_('Group by Voucher')]):
|
||||
and filters.get("group_by") in ['Group by Voucher']):
|
||||
frappe.throw(_("Can not filter based on Voucher No, if grouped by Voucher"))
|
||||
|
||||
if filters.from_date > filters.to_date:
|
||||
@@ -153,7 +153,7 @@ def get_gl_entries(filters, accounting_dimensions):
|
||||
if filters.get("include_dimensions"):
|
||||
order_by_statement = "order by posting_date, creation"
|
||||
|
||||
if filters.get("group_by") == _("Group by Voucher"):
|
||||
if filters.get("group_by") == "Group by Voucher":
|
||||
order_by_statement = "order by posting_date, voucher_type, voucher_no"
|
||||
|
||||
if filters.get("include_default_book_entries"):
|
||||
@@ -312,13 +312,13 @@ def get_data_with_opening_closing(filters, account_details, accounting_dimension
|
||||
# Opening for filtered account
|
||||
data.append(totals.opening)
|
||||
|
||||
if filters.get("group_by") != _('Group by Voucher (Consolidated)'):
|
||||
if filters.get("group_by") != 'Group by Voucher (Consolidated)':
|
||||
for acc, acc_dict in iteritems(gle_map):
|
||||
# acc
|
||||
if acc_dict.entries:
|
||||
# opening
|
||||
data.append({})
|
||||
if filters.get("group_by") != _("Group by Voucher"):
|
||||
if filters.get("group_by") != "Group by Voucher":
|
||||
data.append(acc_dict.totals.opening)
|
||||
|
||||
data += acc_dict.entries
|
||||
@@ -327,7 +327,7 @@ def get_data_with_opening_closing(filters, account_details, accounting_dimension
|
||||
data.append(acc_dict.totals.total)
|
||||
|
||||
# closing
|
||||
if filters.get("group_by") != _("Group by Voucher"):
|
||||
if filters.get("group_by") != "Group by Voucher":
|
||||
data.append(acc_dict.totals.closing)
|
||||
data.append({})
|
||||
else:
|
||||
@@ -357,9 +357,9 @@ def get_totals_dict():
|
||||
)
|
||||
|
||||
def group_by_field(group_by):
|
||||
if group_by == _('Group by Party'):
|
||||
if group_by == 'Group by Party':
|
||||
return 'party'
|
||||
elif group_by in [_('Group by Voucher (Consolidated)'), _('Group by Account')]:
|
||||
elif group_by in ['Group by Voucher (Consolidated)', 'Group by Account']:
|
||||
return 'account'
|
||||
else:
|
||||
return 'voucher_no'
|
||||
@@ -423,9 +423,9 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
|
||||
elif gle.posting_date <= to_date:
|
||||
update_value_in_dict(gle_map[gle.get(group_by)].totals, 'total', gle)
|
||||
update_value_in_dict(totals, 'total', gle)
|
||||
if filters.get("group_by") != _('Group by Voucher (Consolidated)'):
|
||||
if filters.get("group_by") != 'Group by Voucher (Consolidated)':
|
||||
gle_map[gle.get(group_by)].entries.append(gle)
|
||||
elif filters.get("group_by") == _('Group by Voucher (Consolidated)'):
|
||||
elif filters.get("group_by") == 'Group by Voucher (Consolidated)':
|
||||
keylist = [gle.get("voucher_type"), gle.get("voucher_no"), gle.get("account")]
|
||||
for dim in accounting_dimensions:
|
||||
keylist.append(gle.get(dim))
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
frappe.query_reports["Unpaid Expense Claim"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"employee",
|
||||
"fieldname": "employee",
|
||||
"label": __("Employee"),
|
||||
"fieldtype": "Link"
|
||||
"fieldtype": "Link",
|
||||
"options": "Employee"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"fieldname": "supp_master_name",
|
||||
"fieldtype": "Select",
|
||||
"label": "Supplier Naming By",
|
||||
"options": "Supplier Name\nNaming Series"
|
||||
"options": "Supplier Name\nNaming Series\nAuto Name"
|
||||
},
|
||||
{
|
||||
"fieldname": "supplier_group",
|
||||
@@ -123,7 +123,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-24 10:38:28.934525",
|
||||
"modified": "2021-09-08 19:26:23.548837",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Settings",
|
||||
|
||||
@@ -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"));
|
||||
|
||||
|
||||
@@ -394,12 +394,10 @@ def get_item_from_material_requests_based_on_supplier(source_name, target_doc =
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_supplier_tag():
|
||||
if not frappe.cache().hget("Supplier", "Tags"):
|
||||
filters = {"document_type": "Supplier"}
|
||||
tags = list(set(tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag))
|
||||
frappe.cache().hset("Supplier", "Tags", tags)
|
||||
filters = {"document_type": "Supplier"}
|
||||
tags = list(set(tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag))
|
||||
|
||||
return frappe.cache().hget("Supplier", "Tags")
|
||||
return tags
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
|
||||
@@ -433,12 +433,12 @@
|
||||
"image_field": "image",
|
||||
"links": [
|
||||
{
|
||||
"group": "Item Group",
|
||||
"link_doctype": "Supplier Item Group",
|
||||
"link_fieldname": "supplier"
|
||||
"group": "Allowed Items",
|
||||
"link_doctype": "Party Specific Item",
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2021-08-27 18:02:44.314077",
|
||||
"modified": "2021-09-06 17:37:56.522233",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier",
|
||||
|
||||
@@ -10,7 +10,7 @@ from frappe.contacts.address_and_contact import (
|
||||
delete_contact_and_address,
|
||||
load_address_and_contact,
|
||||
)
|
||||
from frappe.model.naming import set_name_by_naming_series
|
||||
from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options
|
||||
|
||||
from erpnext.accounts.party import get_dashboard_info, validate_party_accounts
|
||||
from erpnext.utilities.transaction_base import TransactionBase
|
||||
@@ -40,8 +40,10 @@ class Supplier(TransactionBase):
|
||||
supp_master_name = frappe.defaults.get_global_default('supp_master_name')
|
||||
if supp_master_name == 'Supplier Name':
|
||||
self.name = self.supplier_name
|
||||
else:
|
||||
elif supp_master_name == 'Naming Series':
|
||||
set_name_by_naming_series(self)
|
||||
else:
|
||||
self.name = set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self)
|
||||
|
||||
def on_update(self):
|
||||
if not self.naming_series:
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-05-07 18:16:40.621421",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"supplier",
|
||||
"item_group"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "supplier",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Supplier",
|
||||
"options": "Supplier",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "item_group",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Item Group",
|
||||
"options": "Item Group",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-19 13:48:16.742303",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Item Group",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Purchase User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Purchase Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class SupplierItemGroup(Document):
|
||||
def validate(self):
|
||||
exists = frappe.db.exists({
|
||||
'doctype': 'Supplier Item Group',
|
||||
'supplier': self.supplier,
|
||||
'item_group': self.item_group
|
||||
})
|
||||
if exists:
|
||||
frappe.throw(_("Item Group has already been linked to this supplier."))
|
||||
@@ -7,6 +7,7 @@ import json
|
||||
from collections import defaultdict
|
||||
|
||||
import frappe
|
||||
from frappe import scrub
|
||||
from frappe.desk.reportview import get_filters_cond, get_match_cond
|
||||
from frappe.utils import nowdate, unique
|
||||
|
||||
@@ -223,18 +224,29 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
||||
if not field in searchfields]
|
||||
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
|
||||
|
||||
if filters and isinstance(filters, dict) and filters.get('supplier'):
|
||||
item_group_list = frappe.get_all('Supplier Item Group',
|
||||
filters = {'supplier': filters.get('supplier')}, fields = ['item_group'])
|
||||
if filters and isinstance(filters, dict):
|
||||
if filters.get('customer') or filters.get('supplier'):
|
||||
party = filters.get('customer') or filters.get('supplier')
|
||||
item_rules_list = frappe.get_all('Party Specific Item',
|
||||
filters = {'party': party}, fields = ['restrict_based_on', 'based_on_value'])
|
||||
|
||||
item_groups = []
|
||||
for i in item_group_list:
|
||||
item_groups.append(i.item_group)
|
||||
filters_dict = {}
|
||||
for rule in item_rules_list:
|
||||
if rule['restrict_based_on'] == 'Item':
|
||||
rule['restrict_based_on'] = 'name'
|
||||
filters_dict[rule.restrict_based_on] = []
|
||||
|
||||
del filters['supplier']
|
||||
for rule in item_rules_list:
|
||||
filters_dict[rule.restrict_based_on].append(rule.based_on_value)
|
||||
|
||||
for filter in filters_dict:
|
||||
filters[scrub(filter)] = ['in', filters_dict[filter]]
|
||||
|
||||
if filters.get('customer'):
|
||||
del filters['customer']
|
||||
else:
|
||||
del filters['supplier']
|
||||
|
||||
if item_groups:
|
||||
filters['item_group'] = ['in', item_groups]
|
||||
|
||||
description_cond = ''
|
||||
if frappe.db.count('Item', cache=True) < 50000:
|
||||
@@ -307,7 +319,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)
|
||||
@@ -127,7 +127,7 @@ class ECommerceSettings(Document):
|
||||
if not (new_fields == old_fields):
|
||||
create_website_items_index()
|
||||
|
||||
def validate_cart_settings(doc, method):
|
||||
def validate_cart_settings(doc=None, method=None):
|
||||
frappe.get_doc("E Commerce Settings", "E Commerce Settings").run_method("validate")
|
||||
|
||||
def get_shopping_cart_settings():
|
||||
|
||||
@@ -12,8 +12,6 @@ from frappe.website.doctype.website_slideshow.website_slideshow import get_slide
|
||||
from frappe.website.website_generator import WebsiteGenerator
|
||||
|
||||
from erpnext.e_commerce.doctype.item_review.item_review import get_item_reviews
|
||||
|
||||
# SEARCH
|
||||
from erpnext.e_commerce.redisearch import (
|
||||
delete_item_from_index,
|
||||
insert_item_to_index,
|
||||
@@ -138,10 +136,10 @@ class WebsiteItem(WebsiteGenerator):
|
||||
self.website_image = None
|
||||
|
||||
def make_thumbnail(self):
|
||||
if frappe.flags.in_import:
|
||||
"""Make a thumbnail of `website_image`"""
|
||||
if frappe.flags.in_import or frappe.flags.in_migrate:
|
||||
return
|
||||
|
||||
"""Make a thumbnail of `website_image`"""
|
||||
import requests.exceptions
|
||||
|
||||
if not self.is_new() and self.website_image != frappe.db.get_value(self.doctype, self.name, "website_image"):
|
||||
|
||||
@@ -105,7 +105,7 @@ def place_order():
|
||||
if is_stock_item:
|
||||
item_stock = get_web_item_qty_in_stock(item.item_code, "website_warehouse")
|
||||
if not cint(item_stock.in_stock):
|
||||
throw(_("{1} Not in Stock").format(item.item_code))
|
||||
throw(_("{0} Not in Stock").format(item.item_code))
|
||||
if item.qty > item_stock.stock_qty[0][0]:
|
||||
throw(_("Only {0} in Stock for item {1}").format(item_stock.stock_qty[0][0], item.item_code))
|
||||
|
||||
@@ -168,8 +168,10 @@ def update_cart(item_code, qty, additional_notes=None, with_items=False):
|
||||
return {
|
||||
"items": frappe.render_template("templates/includes/cart/cart_items.html",
|
||||
context),
|
||||
"taxes": frappe.render_template("templates/includes/order/order_taxes.html",
|
||||
"total": frappe.render_template("templates/includes/cart/cart_items_total.html",
|
||||
context),
|
||||
"taxes_and_totals": frappe.render_template("templates/includes/cart/cart_payment_summary.html",
|
||||
context)
|
||||
}
|
||||
else:
|
||||
return {
|
||||
|
||||
@@ -67,12 +67,16 @@ class ItemVariantsCacheManager:
|
||||
as_list=1
|
||||
)
|
||||
|
||||
disabled_items = set([i.name for i in frappe.db.get_all('Item', {'disabled': 1})])
|
||||
unpublished_items = set([i.item_code for i in frappe.db.get_all('Website Item', filters={'published': 0}, fields=["item_code"])])
|
||||
|
||||
attribute_value_item_map = frappe._dict({})
|
||||
item_attribute_value_map = frappe._dict({})
|
||||
|
||||
item_variants_data = [r for r in item_variants_data if r[0] not in disabled_items]
|
||||
# dont consider variants that are unpublished
|
||||
# (either have no Website Item or are unpublished in Website Item)
|
||||
item_variants_data = [r for r in item_variants_data if r[0] not in unpublished_items]
|
||||
item_variants_data = [r for r in item_variants_data if frappe.db.exists("Website Item", {"item_code": r[0]})]
|
||||
|
||||
for row in item_variants_data:
|
||||
item_code, attribute, attribute_value = row
|
||||
# (attr, value) => [item1, item2]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -18,7 +18,7 @@ frappe.ui.form.on('Maintenance Schedule', {
|
||||
},
|
||||
refresh: function (frm) {
|
||||
setTimeout(() => {
|
||||
frm.toggle_display('generate_schedule', !(frm.is_new()));
|
||||
frm.toggle_display('generate_schedule', !(frm.is_new() || frm.doc.docstatus));
|
||||
frm.toggle_display('schedule', !(frm.is_new()));
|
||||
}, 10);
|
||||
},
|
||||
|
||||
@@ -16,9 +16,9 @@ from erpnext.utilities.transaction_base import TransactionBase, delete_events
|
||||
class MaintenanceSchedule(TransactionBase):
|
||||
@frappe.whitelist()
|
||||
def generate_schedule(self):
|
||||
if self.docstatus != 0:
|
||||
return
|
||||
self.set('schedules', [])
|
||||
frappe.db.sql("""delete from `tabMaintenance Schedule Detail`
|
||||
where parent=%s""", (self.name))
|
||||
count = 1
|
||||
for d in self.get('items'):
|
||||
self.validate_maintenance_detail()
|
||||
|
||||
@@ -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' });
|
||||
|
||||
@@ -511,8 +511,14 @@ class BOM(WebsiteGenerator):
|
||||
if d.workstation:
|
||||
self.update_rate_and_time(d, update_hour_rate)
|
||||
|
||||
self.operating_cost += flt(d.operating_cost)
|
||||
self.base_operating_cost += flt(d.base_operating_cost)
|
||||
operating_cost = d.operating_cost
|
||||
base_operating_cost = d.base_operating_cost
|
||||
if d.set_cost_based_on_bom_qty:
|
||||
operating_cost = flt(d.cost_per_unit) * flt(self.quantity)
|
||||
base_operating_cost = flt(d.base_cost_per_unit) * flt(self.quantity)
|
||||
|
||||
self.operating_cost += flt(operating_cost)
|
||||
self.base_operating_cost += flt(base_operating_cost)
|
||||
|
||||
def update_rate_and_time(self, row, update_hour_rate = False):
|
||||
if not row.hour_rate or update_hour_rate:
|
||||
@@ -536,6 +542,8 @@ class BOM(WebsiteGenerator):
|
||||
row.base_hour_rate = flt(row.hour_rate) * flt(self.conversion_rate)
|
||||
row.operating_cost = flt(row.hour_rate) * flt(row.time_in_mins) / 60.0
|
||||
row.base_operating_cost = flt(row.operating_cost) * flt(self.conversion_rate)
|
||||
row.cost_per_unit = row.operating_cost / (row.batch_size or 1.0)
|
||||
row.base_cost_per_unit = row.base_operating_cost / (row.batch_size or 1.0)
|
||||
|
||||
if update_hour_rate:
|
||||
row.db_update()
|
||||
|
||||
@@ -107,6 +107,24 @@ class TestBOM(unittest.TestCase):
|
||||
self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost)
|
||||
self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost)
|
||||
|
||||
def test_bom_cost_with_batch_size(self):
|
||||
bom = frappe.copy_doc(test_records[2])
|
||||
bom.docstatus = 0
|
||||
op_cost = 0.0
|
||||
for op_row in bom.operations:
|
||||
op_row.docstatus = 0
|
||||
op_row.batch_size = 2
|
||||
op_row.set_cost_based_on_bom_qty = 1
|
||||
op_cost += op_row.operating_cost
|
||||
|
||||
bom.save()
|
||||
|
||||
for op_row in bom.operations:
|
||||
self.assertAlmostEqual(op_row.cost_per_unit, op_row.operating_cost / 2)
|
||||
|
||||
self.assertAlmostEqual(bom.operating_cost, op_cost/2)
|
||||
bom.delete()
|
||||
|
||||
def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self):
|
||||
frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1)
|
||||
for item_code, rate in (("_Test Item", 3600), ("_Test Item Home Desktop Manufactured", 3000)):
|
||||
|
||||
@@ -8,15 +8,23 @@
|
||||
"field_order": [
|
||||
"sequence_id",
|
||||
"operation",
|
||||
"workstation",
|
||||
"description",
|
||||
"col_break1",
|
||||
"hour_rate",
|
||||
"workstation",
|
||||
"time_in_mins",
|
||||
"operating_cost",
|
||||
"costing_section",
|
||||
"hour_rate",
|
||||
"base_hour_rate",
|
||||
"column_break_9",
|
||||
"operating_cost",
|
||||
"base_operating_cost",
|
||||
"column_break_11",
|
||||
"batch_size",
|
||||
"set_cost_based_on_bom_qty",
|
||||
"cost_per_unit",
|
||||
"base_cost_per_unit",
|
||||
"more_information_section",
|
||||
"description",
|
||||
"column_break_18",
|
||||
"image"
|
||||
],
|
||||
"fields": [
|
||||
@@ -117,13 +125,59 @@
|
||||
"fieldname": "sequence_id",
|
||||
"fieldtype": "Int",
|
||||
"label": "Sequence ID"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.batch_size > 0 && doc.set_cost_based_on_bom_qty",
|
||||
"fieldname": "cost_per_unit",
|
||||
"fieldtype": "Float",
|
||||
"label": "Cost Per Unit",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "base_cost_per_unit",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"label": "Base Cost Per Unit",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "costing_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Costing"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "more_information_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "More Information"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "set_cost_based_on_bom_qty",
|
||||
"fieldtype": "Check",
|
||||
"label": "Set Operating Cost Based On BOM Quantity"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-12 14:48:09.596843",
|
||||
"modified": "2021-09-13 16:45:01.092868",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM Operation",
|
||||
|
||||
@@ -26,15 +26,23 @@ frappe.ui.form.on('Job Card', {
|
||||
refresh: function(frm) {
|
||||
frappe.flags.pause_job = 0;
|
||||
frappe.flags.resume_job = 0;
|
||||
let has_items = frm.doc.items && frm.doc.items.length;
|
||||
|
||||
if(!frm.doc.__islocal && frm.doc.items && frm.doc.items.length) {
|
||||
if (frm.doc.for_quantity != frm.doc.transferred_qty) {
|
||||
if (!frm.doc.__islocal && has_items && frm.doc.docstatus < 2) {
|
||||
let to_request = frm.doc.for_quantity > frm.doc.transferred_qty;
|
||||
let excess_transfer_allowed = frm.doc.__onload.job_card_excess_transfer;
|
||||
|
||||
if (to_request || excess_transfer_allowed) {
|
||||
frm.add_custom_button(__("Material Request"), () => {
|
||||
frm.trigger("make_material_request");
|
||||
});
|
||||
}
|
||||
|
||||
if (frm.doc.for_quantity != frm.doc.transferred_qty) {
|
||||
// check if any row has untransferred materials
|
||||
// in case of multiple items in JC
|
||||
let to_transfer = frm.doc.items.some((row) => row.transferred_qty < row.required_qty);
|
||||
|
||||
if (to_transfer || excess_transfer_allowed) {
|
||||
frm.add_custom_button(__("Material Transfer"), () => {
|
||||
frm.trigger("make_stock_entry");
|
||||
}).addClass("btn-primary");
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
"total_time_in_mins",
|
||||
"section_break_8",
|
||||
"items",
|
||||
"scrap_items_section",
|
||||
"scrap_items",
|
||||
"corrective_operation_section",
|
||||
"for_job_card",
|
||||
"is_corrective_job_card",
|
||||
@@ -185,7 +187,7 @@
|
||||
"default": "0",
|
||||
"fieldname": "transferred_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Transferred Qty",
|
||||
"label": "FG Qty from Transferred Raw Materials",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -392,14 +394,28 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Batch No",
|
||||
"options": "Batch"
|
||||
},
|
||||
{
|
||||
"fieldname": "scrap_items_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Scrap Items"
|
||||
},
|
||||
{
|
||||
"fieldname": "scrap_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Scrap Items",
|
||||
"no_copy": 1,
|
||||
"options": "Job Card Scrap Item",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-16 15:59:32.766484",
|
||||
"modified": "2021-09-14 00:38:46.873105",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Job Card",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
import json
|
||||
|
||||
@@ -37,6 +34,10 @@ class OperationSequenceError(frappe.ValidationError): pass
|
||||
class JobCardCancelError(frappe.ValidationError): pass
|
||||
|
||||
class JobCard(Document):
|
||||
def onload(self):
|
||||
excess_transfer = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer")
|
||||
self.set_onload("job_card_excess_transfer", excess_transfer)
|
||||
|
||||
def validate(self):
|
||||
self.validate_time_logs()
|
||||
self.set_status()
|
||||
@@ -91,7 +92,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:
|
||||
@@ -449,6 +450,7 @@ class JobCard(Document):
|
||||
frappe.db.set_value('Job Card Item', row.job_card_item, 'transferred_qty', flt(qty))
|
||||
|
||||
def set_transferred_qty(self, update_status=False):
|
||||
"Set total FG Qty for which RM was transferred."
|
||||
if not self.items:
|
||||
self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0
|
||||
|
||||
@@ -457,6 +459,7 @@ class JobCard(Document):
|
||||
return
|
||||
|
||||
if self.items:
|
||||
# sum of 'For Quantity' of Stock Entries against JC
|
||||
self.transferred_qty = frappe.db.get_value('Stock Entry', {
|
||||
'job_card': self.name,
|
||||
'work_order': self.work_order,
|
||||
@@ -500,7 +503,9 @@ class JobCard(Document):
|
||||
self.status = 'Work In Progress'
|
||||
|
||||
if (self.docstatus == 1 and
|
||||
(self.for_quantity == self.transferred_qty or not self.items)):
|
||||
(self.for_quantity <= self.transferred_qty or not self.items)):
|
||||
# consider excess transfer
|
||||
# completed qty is checked via separate validation
|
||||
self.status = 'Completed'
|
||||
|
||||
if self.status != 'Completed':
|
||||
@@ -618,7 +623,11 @@ def make_stock_entry(source_name, target_doc=None):
|
||||
def set_missing_values(source, target):
|
||||
target.purpose = "Material Transfer for Manufacture"
|
||||
target.from_bom = 1
|
||||
target.fg_completed_qty = source.get('for_quantity', 0) - source.get('transferred_qty', 0)
|
||||
|
||||
# avoid negative 'For Quantity'
|
||||
pending_fg_qty = source.get('for_quantity', 0) - source.get('transferred_qty', 0)
|
||||
target.fg_completed_qty = pending_fg_qty if pending_fg_qty > 0 else 0
|
||||
|
||||
target.set_transfer_qty()
|
||||
target.calculate_rate_and_amount()
|
||||
target.set_missing_values()
|
||||
|
||||
@@ -1,78 +1,194 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
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.job_card.job_card import (
|
||||
make_stock_entry as make_stock_entry_from_jc,
|
||||
)
|
||||
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.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
|
||||
class TestJobCard(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
transfer_material_against, source_warehouse = None, None
|
||||
tests_that_transfer_against_jc = ("test_job_card_multiple_materials_transfer",
|
||||
"test_job_card_excess_material_transfer")
|
||||
|
||||
if self._testMethodName in tests_that_transfer_against_jc:
|
||||
transfer_material_against = "Job Card"
|
||||
source_warehouse = "Stores - _TC"
|
||||
|
||||
self.work_order = make_wo_order_test_record(
|
||||
item="_Test FG Item 2",
|
||||
qty=2,
|
||||
transfer_material_against=transfer_material_against,
|
||||
source_warehouse=source_warehouse
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
def test_job_card_multiple_materials_transfer(self):
|
||||
"Test transferring RMs separately against Job Card with multiple RMs."
|
||||
make_stock_entry(
|
||||
item_code="_Test Item",
|
||||
target="Stores - _TC",
|
||||
qty=10,
|
||||
basic_rate=100
|
||||
)
|
||||
make_stock_entry(
|
||||
item_code="_Test Item Home Desktop Manufactured",
|
||||
target="Stores - _TC",
|
||||
qty=6,
|
||||
basic_rate=100
|
||||
)
|
||||
|
||||
job_card_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name})
|
||||
job_card = frappe.get_doc("Job Card", job_card_name)
|
||||
|
||||
transfer_entry_1 = make_stock_entry_from_jc(job_card_name)
|
||||
del transfer_entry_1.items[1] # transfer only 1 of 2 RMs
|
||||
transfer_entry_1.insert()
|
||||
transfer_entry_1.submit()
|
||||
|
||||
job_card.reload()
|
||||
|
||||
self.assertEqual(transfer_entry_1.fg_completed_qty, 2)
|
||||
self.assertEqual(job_card.transferred_qty, 2)
|
||||
|
||||
# transfer second RM
|
||||
transfer_entry_2 = make_stock_entry_from_jc(job_card_name)
|
||||
del transfer_entry_2.items[0]
|
||||
transfer_entry_2.insert()
|
||||
transfer_entry_2.submit()
|
||||
|
||||
# 'For Quantity' here will be 0 since
|
||||
# transfer was made for 2 fg qty in first transfer Stock Entry
|
||||
self.assertEqual(transfer_entry_2.fg_completed_qty, 0)
|
||||
|
||||
def test_job_card_excess_material_transfer(self):
|
||||
"Test transferring more than required RM against Job Card."
|
||||
make_stock_entry(item_code="_Test Item", target="Stores - _TC",
|
||||
qty=25, basic_rate=100)
|
||||
make_stock_entry(item_code="_Test Item Home Desktop Manufactured",
|
||||
target="Stores - _TC", qty=15, basic_rate=100)
|
||||
|
||||
job_card_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name})
|
||||
job_card = frappe.get_doc("Job Card", job_card_name)
|
||||
|
||||
# fully transfer both RMs
|
||||
transfer_entry_1 = make_stock_entry_from_jc(job_card_name)
|
||||
transfer_entry_1.insert()
|
||||
transfer_entry_1.submit()
|
||||
|
||||
# transfer extra qty of both RM due to previously damaged RM
|
||||
transfer_entry_2 = make_stock_entry_from_jc(job_card_name)
|
||||
# deliberately change 'For Quantity'
|
||||
transfer_entry_2.fg_completed_qty = 1
|
||||
transfer_entry_2.items[0].qty = 5
|
||||
transfer_entry_2.items[1].qty = 3
|
||||
transfer_entry_2.insert()
|
||||
transfer_entry_2.submit()
|
||||
|
||||
job_card.reload()
|
||||
self.assertGreater(job_card.transferred_qty, job_card.for_quantity)
|
||||
|
||||
# Check if 'For Quantity' is negative
|
||||
# as 'transferred_qty' > Qty to Manufacture
|
||||
transfer_entry_3 = make_stock_entry_from_jc(job_card_name)
|
||||
self.assertEqual(transfer_entry_3.fg_completed_qty, 0)
|
||||
|
||||
job_card.append("time_logs", {
|
||||
"from_time": "2021-01-01 00:01:00",
|
||||
"to_time": "2021-01-01 06:00:00",
|
||||
"completed_qty": 2
|
||||
})
|
||||
job_card.save()
|
||||
job_card.submit()
|
||||
|
||||
# JC is Completed with excess transfer
|
||||
self.assertEqual(job_card.status, "Completed")
|
||||
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-09-14 00:30:28.533884",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"item_code",
|
||||
"item_name",
|
||||
"column_break_3",
|
||||
"description",
|
||||
"quantity_and_rate",
|
||||
"stock_qty",
|
||||
"column_break_6",
|
||||
"stock_uom"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Scrap Item Code",
|
||||
"options": "Item",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.item_name",
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Scrap Item Name"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.description",
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Description",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "quantity_and_rate",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Quantity and Rate"
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Qty",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.stock_uom",
|
||||
"fieldname": "stock_uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "Stock UOM",
|
||||
"options": "UOM",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-14 01:20:48.588052",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Job Card Scrap Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class JobCardScrapItem(Document):
|
||||
pass
|
||||
@@ -25,9 +25,12 @@
|
||||
"overproduction_percentage_for_sales_order",
|
||||
"column_break_16",
|
||||
"overproduction_percentage_for_work_order",
|
||||
"job_card_section",
|
||||
"add_corrective_operation_cost_in_finished_good_valuation",
|
||||
"column_break_24",
|
||||
"job_card_excess_transfer",
|
||||
"other_settings_section",
|
||||
"update_bom_costs_automatically",
|
||||
"add_corrective_operation_cost_in_finished_good_valuation",
|
||||
"column_break_23",
|
||||
"make_serial_no_batch_from_work_order"
|
||||
],
|
||||
@@ -96,10 +99,10 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Allow multiple material consumptions against a Work Order",
|
||||
"description": "Allow material consumptions without immediately manufacturing finished goods against a Work Order",
|
||||
"fieldname": "material_consumption",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Multiple Material Consumption"
|
||||
"label": "Allow Continuous Material Consumption"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -175,13 +178,29 @@
|
||||
"fieldname": "add_corrective_operation_cost_in_finished_good_valuation",
|
||||
"fieldtype": "Check",
|
||||
"label": "Add Corrective Operation Cost in Finished Good Valuation"
|
||||
},
|
||||
{
|
||||
"fieldname": "job_card_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Job Card"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_24",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Allow transferring raw materials even after the Required Quantity is fulfilled",
|
||||
"fieldname": "job_card_excess_transfer",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Excess Material Transfer"
|
||||
}
|
||||
],
|
||||
"icon": "icon-wrench",
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-16 15:54:38.967341",
|
||||
"modified": "2021-09-13 22:09:09.401559",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Manufacturing Settings",
|
||||
|
||||
@@ -242,6 +242,8 @@ frappe.ui.form.on('Production Plan', {
|
||||
},
|
||||
|
||||
get_sub_assembly_items: function(frm) {
|
||||
frm.dirty();
|
||||
|
||||
frappe.call({
|
||||
method: "get_sub_assembly_items",
|
||||
freeze: true,
|
||||
@@ -434,6 +436,25 @@ frappe.ui.form.on("Material Request Plan Item", {
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Production Plan Sales Order", {
|
||||
sales_order(frm, cdt, cdn) {
|
||||
const { sales_order } = locals[cdt][cdn];
|
||||
if (!sales_order) {
|
||||
return;
|
||||
}
|
||||
frappe.call({
|
||||
method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_so_details",
|
||||
args: { sales_order },
|
||||
callback(r) {
|
||||
const {transaction_date, customer, grand_total} = r.message;
|
||||
frappe.model.set_value(cdt, cdn, 'sales_order_date', transaction_date);
|
||||
frappe.model.set_value(cdt, cdn, 'customer', customer);
|
||||
frappe.model.set_value(cdt, cdn, 'grand_total', grand_total);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
cur_frm.fields_dict['sales_orders'].grid.get_field("sales_order").get_query = function() {
|
||||
return{
|
||||
filters: [
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -561,8 +561,6 @@ class ProductionPlan(Document):
|
||||
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty)
|
||||
self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
|
||||
|
||||
self.save()
|
||||
|
||||
def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None):
|
||||
bom_data = sorted(bom_data, key = lambda i: i.bom_level)
|
||||
|
||||
@@ -735,43 +733,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()
|
||||
@@ -800,6 +797,12 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False):
|
||||
group by item_code, warehouse
|
||||
""".format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_so_details(sales_order):
|
||||
return frappe.db.get_value("Sales Order", sales_order,
|
||||
['transaction_date', 'customer', 'grand_total'], as_dict=1
|
||||
)
|
||||
|
||||
def get_warehouse_list(warehouses):
|
||||
warehouse_list = []
|
||||
|
||||
|
||||
@@ -404,6 +404,7 @@ def make_bom(**args):
|
||||
'uom': item_doc.stock_uom,
|
||||
'stock_uom': item_doc.stock_uom,
|
||||
'rate': item_doc.valuation_rate or args.rate,
|
||||
'source_warehouse': args.source_warehouse
|
||||
})
|
||||
|
||||
if not args.do_not_save:
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
@@ -20,7 +16,7 @@ from erpnext.manufacturing.doctype.work_order.work_order import (
|
||||
stop_unstop,
|
||||
)
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.item.test_item import create_item, make_item
|
||||
from erpnext.stock.doctype.stock_entry import test_stock_entry
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
from erpnext.stock.utils import get_bin
|
||||
@@ -772,6 +768,60 @@ class TestWorkOrder(unittest.TestCase):
|
||||
total_pl_qty
|
||||
)
|
||||
|
||||
def test_job_card_scrap_item(self):
|
||||
items = ['Test FG Item for Scrap Item Test', 'Test RM Item 1 for Scrap Item Test',
|
||||
'Test RM Item 2 for Scrap Item Test']
|
||||
|
||||
company = '_Test Company with perpetual inventory'
|
||||
for item_code in items:
|
||||
create_item(item_code = item_code, is_stock_item = 1,
|
||||
is_purchase_item=1, opening_stock=100, valuation_rate=10, company=company, warehouse='Stores - TCP1')
|
||||
|
||||
item = 'Test FG Item for Scrap Item Test'
|
||||
raw_materials = ['Test RM Item 1 for Scrap Item Test', 'Test RM Item 2 for Scrap Item Test']
|
||||
if not frappe.db.get_value('BOM', {'item': item}):
|
||||
bom = make_bom(item=item, source_warehouse='Stores - TCP1', raw_materials=raw_materials, do_not_save=True)
|
||||
bom.with_operations = 1
|
||||
bom.append('operations', {
|
||||
'operation': '_Test Operation 1',
|
||||
'workstation': '_Test Workstation 1',
|
||||
'hour_rate': 20,
|
||||
'time_in_mins': 60
|
||||
})
|
||||
|
||||
bom.submit()
|
||||
|
||||
wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1)
|
||||
job_card = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name')
|
||||
update_job_card(job_card)
|
||||
|
||||
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
|
||||
for row in stock_entry.items:
|
||||
if row.is_scrap_item:
|
||||
self.assertEqual(row.qty, 1)
|
||||
|
||||
def update_job_card(job_card):
|
||||
job_card_doc = frappe.get_doc('Job Card', job_card)
|
||||
job_card_doc.set('scrap_items', [
|
||||
{
|
||||
'item_code': 'Test RM Item 1 for Scrap Item Test',
|
||||
'stock_qty': 2
|
||||
},
|
||||
{
|
||||
'item_code': 'Test RM Item 2 for Scrap Item Test',
|
||||
'stock_qty': 2
|
||||
},
|
||||
])
|
||||
|
||||
job_card_doc.append('time_logs', {
|
||||
'from_time': now(),
|
||||
'time_in_mins': 60,
|
||||
'completed_qty': job_card_doc.for_quantity
|
||||
})
|
||||
|
||||
job_card_doc.submit()
|
||||
|
||||
|
||||
def get_scrap_item_details(bom_no):
|
||||
scrap_items = {}
|
||||
for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`
|
||||
@@ -814,6 +864,7 @@ def make_wo_order_test_record(**args):
|
||||
wo_order.get_items_and_operations_from_bom()
|
||||
wo_order.sales_order = args.sales_order or None
|
||||
wo_order.planned_start_date = args.planned_start_date or now()
|
||||
wo_order.transfer_material_against = args.transfer_material_against or "Work Order"
|
||||
|
||||
if args.source_warehouse:
|
||||
for item in wo_order.get("required_items"):
|
||||
|
||||
@@ -306,8 +306,11 @@ 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
|
||||
erpnext.patches.v13_0.populate_e_commerce_settings
|
||||
erpnext.patches.v13_0.make_homepage_products_website_items
|
||||
erpnext.patches.v13_0.update_dates_in_tax_withholding_category
|
||||
erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item
|
||||
|
||||
@@ -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)
|
||||
@@ -0,0 +1,17 @@
|
||||
# Copyright (c) 2019, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
if frappe.db.table_exists('Supplier Item Group'):
|
||||
frappe.reload_doc("selling", "doctype", "party_specific_item")
|
||||
sig = frappe.db.get_all("Supplier Item Group", fields=["name", "supplier", "item_group"])
|
||||
for item in sig:
|
||||
psi = frappe.new_doc("Party Specific Item")
|
||||
psi.party_type = "Supplier"
|
||||
psi.party = item.supplier
|
||||
psi.restrict_based_on = "Item Group"
|
||||
psi.based_on_value = item.item_group
|
||||
psi.insert()
|
||||
@@ -0,0 +1,26 @@
|
||||
# Copyright (c) 2021, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc('accounts', 'doctype', 'Tax Withholding Rate')
|
||||
|
||||
if frappe.db.has_column('Tax Withholding Rate', 'fiscal_year'):
|
||||
tds_category_rates = frappe.get_all('Tax Withholding Rate', fields=['name', 'fiscal_year'])
|
||||
|
||||
fiscal_year_map = {}
|
||||
fiscal_year_details = frappe.get_all('Fiscal Year', fields=['name', 'year_start_date', 'year_end_date'])
|
||||
|
||||
for d in fiscal_year_details:
|
||||
fiscal_year_map.setdefault(d.name, d)
|
||||
|
||||
for rate in tds_category_rates:
|
||||
from_date = fiscal_year_map.get(rate.fiscal_year).get('year_start_date')
|
||||
to_date = fiscal_year_map.get(rate.fiscal_year).get('year_end_date')
|
||||
|
||||
frappe.db.set_value('Tax Withholding Rate', rate.name, {
|
||||
'from_date': from_date,
|
||||
'to_date': to_date
|
||||
})
|
||||
@@ -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",
|
||||
|
||||
@@ -105,6 +105,8 @@ $.extend(shopping_cart, {
|
||||
},
|
||||
|
||||
set_cart_count: function(animate=false) {
|
||||
$(".intermediate-empty-cart").remove();
|
||||
|
||||
var cart_count = frappe.get_cookie("cart_count");
|
||||
if(frappe.session.user==="Guest") {
|
||||
cart_count = 0;
|
||||
@@ -119,13 +121,20 @@ $.extend(shopping_cart, {
|
||||
|
||||
if(parseInt(cart_count) === 0 || cart_count === undefined) {
|
||||
$cart.css("display", "none");
|
||||
$(".cart-items").html('Cart is Empty');
|
||||
$(".cart-tax-items").hide();
|
||||
$(".btn-place-order").hide();
|
||||
$(".cart-payment-addresses").hide();
|
||||
|
||||
let intermediate_empty_cart_msg = `
|
||||
<div class="text-center w-100 intermediate-empty-cart mt-4 mb-4 text-muted">
|
||||
${ __("Cart is Empty") }
|
||||
</div>
|
||||
`;
|
||||
$(".cart-table").after(intermediate_empty_cart_msg);
|
||||
}
|
||||
else {
|
||||
$cart.css("display", "inline");
|
||||
$("#cart-count").text(cart_count);
|
||||
}
|
||||
|
||||
if(cart_count) {
|
||||
@@ -152,7 +161,10 @@ $.extend(shopping_cart, {
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
$(".cart-items").html(r.message.items);
|
||||
$(".cart-tax-items").html(r.message.taxes);
|
||||
$(".cart-tax-items").html(r.message.total);
|
||||
$(".payment-summary").html(r.message.taxes_and_totals);
|
||||
shopping_cart.set_cart_count();
|
||||
|
||||
if (cart_dropdown != true) {
|
||||
$(".cart-icon").hide();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -1,11 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
|
||||
class TestSupplierItemGroup(unittest.TestCase):
|
||||
class TestProductTaxCategory(unittest.TestCase):
|
||||
pass
|
||||
@@ -803,11 +803,11 @@ def set_tax_withholding_category(company):
|
||||
accounts = [dict(company=company, account=tds_account)]
|
||||
|
||||
try:
|
||||
fiscal_year = get_fiscal_year(today(), verbose=0, company=company)[0]
|
||||
fiscal_year_details = get_fiscal_year(today(), verbose=0, company=company)
|
||||
except FiscalYearError:
|
||||
pass
|
||||
|
||||
docs = get_tds_details(accounts, fiscal_year)
|
||||
docs = get_tds_details(accounts, fiscal_year_details)
|
||||
|
||||
for d in docs:
|
||||
if not frappe.db.exists("Tax Withholding Category", d.get("name")):
|
||||
@@ -822,9 +822,10 @@ def set_tax_withholding_category(company):
|
||||
if accounts:
|
||||
doc.append("accounts", accounts[0])
|
||||
|
||||
if fiscal_year:
|
||||
if fiscal_year_details:
|
||||
# if fiscal year don't match with any of the already entered data, append rate row
|
||||
fy_exist = [k for k in doc.get('rates') if k.get('fiscal_year')==fiscal_year]
|
||||
fy_exist = [k for k in doc.get('rates') if k.get('from_date') <= fiscal_year_details[1] \
|
||||
and k.get('to_date') >= fiscal_year_details[2]]
|
||||
if not fy_exist:
|
||||
doc.append("rates", d.get('rates')[0])
|
||||
|
||||
@@ -847,149 +848,149 @@ def set_tds_account(docs, company):
|
||||
}
|
||||
])
|
||||
|
||||
def get_tds_details(accounts, fiscal_year):
|
||||
def get_tds_details(accounts, fiscal_year_details):
|
||||
# bootstrap default tax withholding sections
|
||||
return [
|
||||
dict(name="TDS - 194C - Company",
|
||||
category_name="Payment to Contractors (Single / Aggregate)",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 2,
|
||||
"single_threshold": 30000, "cumulative_threshold": 100000}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 2, "single_threshold": 30000, "cumulative_threshold": 100000}]),
|
||||
dict(name="TDS - 194C - Individual",
|
||||
category_name="Payment to Contractors (Single / Aggregate)",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 1,
|
||||
"single_threshold": 30000, "cumulative_threshold": 100000}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 1, "single_threshold": 30000, "cumulative_threshold": 100000}]),
|
||||
dict(name="TDS - 194C - No PAN / Invalid PAN",
|
||||
category_name="Payment to Contractors (Single / Aggregate)",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
|
||||
"single_threshold": 30000, "cumulative_threshold": 100000}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 20, "single_threshold": 30000, "cumulative_threshold": 100000}]),
|
||||
dict(name="TDS - 194D - Company",
|
||||
category_name="Insurance Commission",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5,
|
||||
"single_threshold": 15000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 5, "single_threshold": 15000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194D - Company Assessee",
|
||||
category_name="Insurance Commission",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10,
|
||||
"single_threshold": 15000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 10, "single_threshold": 15000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194D - Individual",
|
||||
category_name="Insurance Commission",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5,
|
||||
"single_threshold": 15000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 5, "single_threshold": 15000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194D - No PAN / Invalid PAN",
|
||||
category_name="Insurance Commission",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
|
||||
"single_threshold": 15000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 20, "single_threshold": 15000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194DA - Company",
|
||||
category_name="Non-exempt payments made under a life insurance policy",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 1,
|
||||
"single_threshold": 100000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 1, "single_threshold": 100000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194DA - Individual",
|
||||
category_name="Non-exempt payments made under a life insurance policy",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 1,
|
||||
"single_threshold": 100000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 1, "single_threshold": 100000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194DA - No PAN / Invalid PAN",
|
||||
category_name="Non-exempt payments made under a life insurance policy",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
|
||||
"single_threshold": 100000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 20, "single_threshold": 100000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194H - Company",
|
||||
category_name="Commission / Brokerage",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5,
|
||||
"single_threshold": 15000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 5, "single_threshold": 15000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194H - Individual",
|
||||
category_name="Commission / Brokerage",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5,
|
||||
"single_threshold": 15000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 5, "single_threshold": 15000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194H - No PAN / Invalid PAN",
|
||||
category_name="Commission / Brokerage",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
|
||||
"single_threshold": 15000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 20, "single_threshold": 15000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194I - Rent - Company",
|
||||
category_name="Rent",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10,
|
||||
"single_threshold": 180000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 10, "single_threshold": 180000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194I - Rent - Individual",
|
||||
category_name="Rent",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10,
|
||||
"single_threshold": 180000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 10, "single_threshold": 180000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194I - Rent - No PAN / Invalid PAN",
|
||||
category_name="Rent",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
|
||||
"single_threshold": 180000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 20, "single_threshold": 180000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194I - Rent/Machinery - Company",
|
||||
category_name="Rent-Plant / Machinery",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 2,
|
||||
"single_threshold": 180000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 2, "single_threshold": 180000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194I - Rent/Machinery - Individual",
|
||||
category_name="Rent-Plant / Machinery",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 2,
|
||||
"single_threshold": 180000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 2, "single_threshold": 180000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194I - Rent/Machinery - No PAN / Invalid PAN",
|
||||
category_name="Rent-Plant / Machinery",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
|
||||
"single_threshold": 180000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 20, "single_threshold": 180000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194J - Professional Fees - Company",
|
||||
category_name="Professional Fees",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10,
|
||||
"single_threshold": 30000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 10, "single_threshold": 30000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194J - Professional Fees - Individual",
|
||||
category_name="Professional Fees",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10,
|
||||
"single_threshold": 30000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 10, "single_threshold": 30000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194J - Professional Fees - No PAN / Invalid PAN",
|
||||
category_name="Professional Fees",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
|
||||
"single_threshold": 30000, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 20, "single_threshold": 30000, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194J - Director Fees - Company",
|
||||
category_name="Director Fees",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10,
|
||||
"single_threshold": 0, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 10, "single_threshold": 0, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194J - Director Fees - Individual",
|
||||
category_name="Director Fees",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10,
|
||||
"single_threshold": 0, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 10, "single_threshold": 0, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194J - Director Fees - No PAN / Invalid PAN",
|
||||
category_name="Director Fees",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
|
||||
"single_threshold": 0, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 20, "single_threshold": 0, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194 - Dividends - Company",
|
||||
category_name="Dividends",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10,
|
||||
"single_threshold": 2500, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 10, "single_threshold": 2500, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194 - Dividends - Individual",
|
||||
category_name="Dividends",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10,
|
||||
"single_threshold": 2500, "cumulative_threshold": 0}]),
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 10, "single_threshold": 2500, "cumulative_threshold": 0}]),
|
||||
dict(name="TDS - 194 - Dividends - No PAN / Invalid PAN",
|
||||
category_name="Dividends",
|
||||
doctype="Tax Withholding Category", accounts=accounts,
|
||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
|
||||
"single_threshold": 2500, "cumulative_threshold": 0}])
|
||||
rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2],
|
||||
"tax_withholding_rate": 20, "single_threshold": 2500, "cumulative_threshold": 0}])
|
||||
]
|
||||
|
||||
def create_gratuity_rule():
|
||||
|
||||
@@ -96,35 +96,36 @@ class Gstr1Report(object):
|
||||
def get_b2c_data(self):
|
||||
b2cs_output = {}
|
||||
|
||||
for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
|
||||
invoice_details = self.invoices.get(inv)
|
||||
for rate, items in items_based_on_rate.items():
|
||||
place_of_supply = invoice_details.get("place_of_supply")
|
||||
ecommerce_gstin = invoice_details.get("ecommerce_gstin")
|
||||
if self.invoices:
|
||||
for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
|
||||
invoice_details = self.invoices.get(inv)
|
||||
for rate, items in items_based_on_rate.items():
|
||||
place_of_supply = invoice_details.get("place_of_supply")
|
||||
ecommerce_gstin = invoice_details.get("ecommerce_gstin")
|
||||
|
||||
b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin),{
|
||||
"place_of_supply": "",
|
||||
"ecommerce_gstin": "",
|
||||
"rate": "",
|
||||
"taxable_value": 0,
|
||||
"cess_amount": 0,
|
||||
"type": "",
|
||||
"invoice_number": invoice_details.get("invoice_number"),
|
||||
"posting_date": invoice_details.get("posting_date"),
|
||||
"invoice_value": invoice_details.get("base_grand_total"),
|
||||
})
|
||||
b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin), {
|
||||
"place_of_supply": "",
|
||||
"ecommerce_gstin": "",
|
||||
"rate": "",
|
||||
"taxable_value": 0,
|
||||
"cess_amount": 0,
|
||||
"type": "",
|
||||
"invoice_number": invoice_details.get("invoice_number"),
|
||||
"posting_date": invoice_details.get("posting_date"),
|
||||
"invoice_value": invoice_details.get("base_grand_total"),
|
||||
})
|
||||
|
||||
row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin))
|
||||
row["place_of_supply"] = place_of_supply
|
||||
row["ecommerce_gstin"] = ecommerce_gstin
|
||||
row["rate"] = rate
|
||||
row["taxable_value"] += sum([abs(net_amount)
|
||||
for item_code, net_amount in self.invoice_items.get(inv).items() if item_code in items])
|
||||
row["cess_amount"] += flt(self.invoice_cess.get(inv), 2)
|
||||
row["type"] = "E" if ecommerce_gstin else "OE"
|
||||
row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin))
|
||||
row["place_of_supply"] = place_of_supply
|
||||
row["ecommerce_gstin"] = ecommerce_gstin
|
||||
row["rate"] = rate
|
||||
row["taxable_value"] += sum([abs(net_amount)
|
||||
for item_code, net_amount in self.invoice_items.get(inv).items() if item_code in items])
|
||||
row["cess_amount"] += flt(self.invoice_cess.get(inv), 2)
|
||||
row["type"] = "E" if ecommerce_gstin else "OE"
|
||||
|
||||
for key, value in iteritems(b2cs_output):
|
||||
self.data.append(value)
|
||||
for key, value in iteritems(b2cs_output):
|
||||
self.data.append(value)
|
||||
|
||||
def get_row_data_for_invoice(self, invoice, invoice_details, tax_rate, items):
|
||||
row = []
|
||||
@@ -173,9 +174,10 @@ class Gstr1Report(object):
|
||||
|
||||
company_gstins = get_company_gstin_number(self.filters.get('company'), all_gstins=True)
|
||||
|
||||
self.filters.update({
|
||||
'company_gstins': company_gstins
|
||||
})
|
||||
if company_gstins:
|
||||
self.filters.update({
|
||||
'company_gstins': company_gstins
|
||||
})
|
||||
|
||||
invoice_data = frappe.db.sql("""
|
||||
select
|
||||
@@ -212,7 +214,7 @@ class Gstr1Report(object):
|
||||
|
||||
|
||||
if self.filters.get("type_of_business") == "B2B":
|
||||
conditions += "AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') AND is_return != 1"
|
||||
conditions += "AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') AND is_return != 1 AND is_debit_note !=1"
|
||||
|
||||
if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"):
|
||||
b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit')
|
||||
@@ -221,7 +223,7 @@ class Gstr1Report(object):
|
||||
|
||||
if self.filters.get("type_of_business") == "B2C Large":
|
||||
conditions += """ AND ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'')
|
||||
AND grand_total > {0} AND is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit))
|
||||
AND grand_total > {0} AND is_return != 1 AND is_debit_note !=1 AND gst_category ='Unregistered' """.format(flt(b2c_limit))
|
||||
|
||||
elif self.filters.get("type_of_business") == "B2C Small":
|
||||
conditions += """ AND (
|
||||
@@ -234,8 +236,8 @@ class Gstr1Report(object):
|
||||
elif self.filters.get("type_of_business") == "CDNR-UNREG":
|
||||
b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit')
|
||||
conditions += """ AND ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'')
|
||||
AND ABS(grand_total) > {0} AND (is_return = 1 OR is_debit_note = 1)
|
||||
AND IFNULL(gst_category, '') in ('Unregistered', 'Overseas')""".format(flt(b2c_limit))
|
||||
AND (is_return = 1 OR is_debit_note = 1)
|
||||
AND IFNULL(gst_category, '') in ('Unregistered', 'Overseas')"""
|
||||
|
||||
elif self.filters.get("type_of_business") == "EXPORT":
|
||||
conditions += """ AND is_return !=1 and gst_category = 'Overseas' """
|
||||
@@ -1050,6 +1052,7 @@ def get_company_gstin_number(company, address=None, all_gstins=False):
|
||||
["Dynamic Link", "link_doctype", "=", "Company"],
|
||||
["Dynamic Link", "link_name", "=", company],
|
||||
["Dynamic Link", "parenttype", "=", "Address"],
|
||||
["gstin", "!=", '']
|
||||
]
|
||||
gstin = frappe.get_all("Address", filters=filters, pluck="gstin", order_by="is_primary_address desc")
|
||||
if gstin and not all_gstins:
|
||||
|
||||
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)
|
||||
|
||||
@@ -510,8 +510,14 @@
|
||||
"idx": 363,
|
||||
"image_field": "image",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-25 18:56:09.929905",
|
||||
"links": [
|
||||
{
|
||||
"group": "Allowed Items",
|
||||
"link_doctype": "Party Specific Item",
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2021-09-06 17:38:54.196663",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Customer",
|
||||
|
||||
@@ -14,7 +14,7 @@ from frappe.contacts.address_and_contact import (
|
||||
)
|
||||
from frappe.desk.reportview import build_match_conditions, get_filters_cond
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.model.naming import set_name_by_naming_series
|
||||
from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options
|
||||
from frappe.model.rename_doc import update_linked_doctypes
|
||||
from frappe.utils import cint, cstr, flt, get_formatted_email, today
|
||||
from frappe.utils.user import get_users_with_role
|
||||
@@ -40,8 +40,10 @@ class Customer(TransactionBase):
|
||||
cust_master_name = frappe.defaults.get_global_default('cust_master_name')
|
||||
if cust_master_name == 'Customer Name':
|
||||
self.name = self.get_customer_name()
|
||||
else:
|
||||
elif cust_master_name == 'Naming Series':
|
||||
set_name_by_naming_series(self)
|
||||
else:
|
||||
self.name = set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self)
|
||||
|
||||
def get_customer_name(self):
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Supplier Item Group', {
|
||||
frappe.ui.form.on('Party Specific Item', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-08-27 19:28:07.559978",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"party_type",
|
||||
"party",
|
||||
"column_break_3",
|
||||
"restrict_based_on",
|
||||
"based_on_value"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "party_type",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Party Type",
|
||||
"options": "Customer\nSupplier",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "party",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Party Name",
|
||||
"options": "party_type",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "restrict_based_on",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Restrict Items Based On",
|
||||
"options": "Item\nItem Group\nBrand",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "based_on_value",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Based On Value",
|
||||
"options": "restrict_based_on",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-14 13:27:58.612334",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Party Specific Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "party",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class PartySpecificItem(Document):
|
||||
def validate(self):
|
||||
exists = frappe.db.exists({
|
||||
'doctype': 'Party Specific Item',
|
||||
'party_type': self.party_type,
|
||||
'party': self.party,
|
||||
'restrict_based_on': self.restrict_based_on,
|
||||
'based_on': self.based_on_value,
|
||||
})
|
||||
if exists:
|
||||
frappe.throw(_("This item filter has already been applied for the {0}").format(self.party_type))
|
||||
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.controllers.queries import item_query
|
||||
|
||||
test_dependencies = ['Item', 'Customer', 'Supplier']
|
||||
|
||||
def create_party_specific_item(**args):
|
||||
psi = frappe.new_doc("Party Specific Item")
|
||||
psi.party_type = args.get('party_type')
|
||||
psi.party = args.get('party')
|
||||
psi.restrict_based_on = args.get('restrict_based_on')
|
||||
psi.based_on_value = args.get('based_on_value')
|
||||
psi.insert()
|
||||
|
||||
class TestPartySpecificItem(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.customer = frappe.get_last_doc("Customer")
|
||||
self.supplier = frappe.get_last_doc("Supplier")
|
||||
self.item = frappe.get_last_doc("Item")
|
||||
|
||||
def test_item_query_for_customer(self):
|
||||
create_party_specific_item(party_type='Customer', party=self.customer.name, restrict_based_on='Item', based_on_value=self.item.name)
|
||||
filters = {'is_sales_item': 1, 'customer': self.customer.name}
|
||||
items = item_query(doctype= 'Item', txt= '', searchfield= 'name', start= 0, page_len= 20,filters=filters, as_dict= False)
|
||||
for item in items:
|
||||
self.assertEqual(item[0], self.item.name)
|
||||
|
||||
def test_item_query_for_supplier(self):
|
||||
create_party_specific_item(party_type='Supplier', party=self.supplier.name, restrict_based_on='Item Group', based_on_value=self.item.item_group)
|
||||
filters = {'supplier': self.supplier.name, 'is_purchase_item': 1}
|
||||
items = item_query(doctype= 'Item', txt= '', searchfield= 'name', start= 0, page_len= 20,filters=filters, as_dict= False)
|
||||
for item in items:
|
||||
self.assertEqual(item[2], self.item.item_group)
|
||||
@@ -41,14 +41,14 @@
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Customer Naming By",
|
||||
"options": "Customer Name\nNaming Series"
|
||||
"options": "Customer Name\nNaming Series\nAuto Name"
|
||||
},
|
||||
{
|
||||
"fieldname": "campaign_naming_by",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Campaign Naming By",
|
||||
"options": "Campaign Name\nNaming Series"
|
||||
"options": "Campaign Name\nNaming Series\nAuto Name"
|
||||
},
|
||||
{
|
||||
"fieldname": "customer_group",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -63,7 +63,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
|
||||
this.frm.set_query("item_code", "items", function() {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
filters: {'is_sales_item': 1}
|
||||
filters: {'is_sales_item': 1, 'customer': cur_frm.doc.customer}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
from collections import defaultdict
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
@@ -684,7 +685,7 @@ class StockEntry(StockController):
|
||||
|
||||
def validate_bom(self):
|
||||
for d in self.get('items'):
|
||||
if d.bom_no and (d.t_warehouse != getattr(self, "pro_doc", frappe._dict()).scrap_warehouse):
|
||||
if d.bom_no and d.is_finished_item:
|
||||
item_code = d.original_item or d.item_code
|
||||
validate_bom_no(item_code, d.bom_no)
|
||||
|
||||
@@ -1191,13 +1192,88 @@ class StockEntry(StockController):
|
||||
|
||||
# item dict = { item_code: {qty, description, stock_uom} }
|
||||
item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=qty,
|
||||
fetch_exploded = 0, fetch_scrap_items = 1)
|
||||
fetch_exploded = 0, fetch_scrap_items = 1) or {}
|
||||
|
||||
for item in itervalues(item_dict):
|
||||
item.from_warehouse = ""
|
||||
item.is_scrap_item = 1
|
||||
|
||||
for row in self.get_scrap_items_from_job_card():
|
||||
if row.stock_qty <= 0:
|
||||
continue
|
||||
|
||||
item_row = item_dict.get(row.item_code)
|
||||
if not item_row:
|
||||
item_row = frappe._dict({})
|
||||
|
||||
item_row.update({
|
||||
'uom': row.stock_uom,
|
||||
'from_warehouse': '',
|
||||
'qty': row.stock_qty + flt(item_row.stock_qty),
|
||||
'converison_factor': 1,
|
||||
'is_scrap_item': 1,
|
||||
'item_name': row.item_name,
|
||||
'description': row.description,
|
||||
'allow_zero_valuation_rate': 1
|
||||
})
|
||||
|
||||
item_dict[row.item_code] = item_row
|
||||
|
||||
return item_dict
|
||||
|
||||
def get_scrap_items_from_job_card(self):
|
||||
if not self.pro_doc:
|
||||
self.set_work_order_details()
|
||||
|
||||
scrap_items = frappe.db.sql('''
|
||||
SELECT
|
||||
JCSI.item_code, JCSI.item_name, SUM(JCSI.stock_qty) as stock_qty, JCSI.stock_uom, JCSI.description
|
||||
FROM
|
||||
`tabJob Card` JC, `tabJob Card Scrap Item` JCSI
|
||||
WHERE
|
||||
JCSI.parent = JC.name AND JC.docstatus = 1
|
||||
AND JCSI.item_code IS NOT NULL AND JC.work_order = %s
|
||||
GROUP BY
|
||||
JCSI.item_code
|
||||
''', self.work_order, as_dict=1)
|
||||
|
||||
pending_qty = flt(self.pro_doc.qty) - flt(self.pro_doc.produced_qty)
|
||||
if pending_qty <=0:
|
||||
return []
|
||||
|
||||
used_scrap_items = self.get_used_scrap_items()
|
||||
for row in scrap_items:
|
||||
row.stock_qty -= flt(used_scrap_items.get(row.item_code))
|
||||
row.stock_qty = (row.stock_qty) * flt(self.fg_completed_qty) / flt(pending_qty)
|
||||
|
||||
if used_scrap_items.get(row.item_code):
|
||||
used_scrap_items[row.item_code] -= row.stock_qty
|
||||
|
||||
if cint(frappe.get_cached_value('UOM', row.stock_uom, 'must_be_whole_number')):
|
||||
row.stock_qty = frappe.utils.ceil(row.stock_qty)
|
||||
|
||||
return scrap_items
|
||||
|
||||
def get_used_scrap_items(self):
|
||||
used_scrap_items = defaultdict(float)
|
||||
data = frappe.get_all(
|
||||
'Stock Entry',
|
||||
fields = [
|
||||
'`tabStock Entry Detail`.`item_code`', '`tabStock Entry Detail`.`qty`'
|
||||
],
|
||||
filters = [
|
||||
['Stock Entry', 'work_order', '=', self.work_order],
|
||||
['Stock Entry Detail', 'is_scrap_item', '=', 1],
|
||||
['Stock Entry', 'docstatus', '=', 1],
|
||||
['Stock Entry', 'purpose', 'in', ['Repack', 'Manufacture']]
|
||||
]
|
||||
)
|
||||
|
||||
for row in data:
|
||||
used_scrap_items[row.item_code] += row.qty
|
||||
|
||||
return used_scrap_items
|
||||
|
||||
def get_unconsumed_raw_materials(self):
|
||||
wo = frappe.get_doc("Work Order", self.work_order)
|
||||
wo_items = frappe.get_all('Work Order Item',
|
||||
@@ -1264,9 +1340,9 @@ class StockEntry(StockController):
|
||||
po_qty = frappe.db.sql("""select qty, produced_qty, material_transferred_for_manufacturing from
|
||||
`tabWork Order` where name=%s""", self.work_order, as_dict=1)[0]
|
||||
|
||||
manufacturing_qty = flt(po_qty.qty)
|
||||
manufacturing_qty = flt(po_qty.qty) or 1
|
||||
produced_qty = flt(po_qty.produced_qty)
|
||||
trans_qty = flt(po_qty.material_transferred_for_manufacturing)
|
||||
trans_qty = flt(po_qty.material_transferred_for_manufacturing) or 1
|
||||
|
||||
for item in transferred_materials:
|
||||
qty= item.qty
|
||||
@@ -1417,8 +1493,8 @@ class StockEntry(StockController):
|
||||
se_child.is_scrap_item = item_dict[d].get("is_scrap_item", 0)
|
||||
se_child.is_process_loss = item_dict[d].get("is_process_loss", 0)
|
||||
|
||||
for field in ["idx", "po_detail", "original_item",
|
||||
"expense_account", "description", "item_name", "serial_no", "batch_no"]:
|
||||
for field in ["idx", "po_detail", "original_item", "expense_account",
|
||||
"description", "item_name", "serial_no", "batch_no", "allow_zero_valuation_rate"]:
|
||||
if item_dict[d].get(field):
|
||||
se_child.set(field, item_dict[d].get(field))
|
||||
|
||||
|
||||
@@ -592,6 +592,11 @@ def get_stock_balance_for(item_code, warehouse,
|
||||
item_dict = frappe.db.get_value("Item", item_code,
|
||||
["has_serial_no", "has_batch_no"], as_dict=1)
|
||||
|
||||
if not item_dict:
|
||||
# In cases of data upload to Items table
|
||||
msg = _("Item {} does not exist.").format(item_code)
|
||||
frappe.throw(msg, title=_("Missing"))
|
||||
|
||||
serial_nos = ""
|
||||
with_serial_no = True if item_dict.get("has_serial_no") else False
|
||||
data = get_stock_balance(item_code, warehouse, posting_date, posting_time,
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
63
erpnext/stock/report/test_reports.py
Normal file
63
erpnext/stock/report/test_reports.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import unittest
|
||||
from typing import List, Tuple
|
||||
|
||||
from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report
|
||||
|
||||
DEFAULT_FILTERS = {
|
||||
"company": "_Test Company",
|
||||
"from_date": "2010-01-01",
|
||||
"to_date": "2030-01-01",
|
||||
}
|
||||
|
||||
|
||||
REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [
|
||||
("Stock Ledger", {"_optional": True}),
|
||||
("Stock Balance", {"_optional": True}),
|
||||
("Stock Projected Qty", {"_optional": True}),
|
||||
("Batch-Wise Balance History", {}),
|
||||
("Itemwise Recommended Reorder Level", {"item_group": "All Item Groups"}),
|
||||
("COGS By Item Group", {}),
|
||||
("Stock Qty vs Serial No Count", {"warehouse": "_Test Warehouse - _TC"}),
|
||||
(
|
||||
"Stock and Account Value Comparison",
|
||||
{
|
||||
"company": "_Test Company with perpetual inventory",
|
||||
"account": "Stock In Hand - TCP1",
|
||||
"as_on_date": "2021-01-01",
|
||||
},
|
||||
),
|
||||
("Product Bundle Balance", {"date": "2022-01-01", "_optional": True}),
|
||||
(
|
||||
"Stock Analytics",
|
||||
{
|
||||
"from_date": "2021-01-01",
|
||||
"to_date": "2021-12-31",
|
||||
"value_quantity": "Quantity",
|
||||
"_optional": True,
|
||||
},
|
||||
),
|
||||
("Warehouse wise Item Balance Age and Value", {"_optional": True}),
|
||||
("Item Variant Details", {"item": "_Test Variant Item",}),
|
||||
("Total Stock Summary", {"group_by": "warehouse",}),
|
||||
("Batch Item Expiry Status", {}),
|
||||
("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}),
|
||||
]
|
||||
|
||||
OPTIONAL_FILTERS = {
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"item": "_Test Item",
|
||||
"item_group": "_Test Item Group",
|
||||
}
|
||||
|
||||
|
||||
class TestReports(unittest.TestCase):
|
||||
def test_execute_all_stock_reports(self):
|
||||
"""Test that all script report in stock modules are executable with supported filters"""
|
||||
for report, filter in REPORT_FILTER_TEST_CASES:
|
||||
execute_script_report(
|
||||
report_name=report,
|
||||
module="Stock",
|
||||
filters=filter,
|
||||
default_filters=DEFAULT_FILTERS,
|
||||
optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None,
|
||||
)
|
||||
@@ -399,7 +399,8 @@ class update_entries_after(object):
|
||||
return
|
||||
|
||||
# Get dynamic incoming/outgoing rate
|
||||
self.get_dynamic_incoming_outgoing_rate(sle)
|
||||
if not self.args.get("sle_id"):
|
||||
self.get_dynamic_incoming_outgoing_rate(sle)
|
||||
|
||||
if sle.serial_no:
|
||||
self.get_serialized_values(sle)
|
||||
@@ -439,7 +440,8 @@ class update_entries_after(object):
|
||||
sle.doctype="Stock Ledger Entry"
|
||||
frappe.get_doc(sle).db_update()
|
||||
|
||||
self.update_outgoing_rate_on_transaction(sle)
|
||||
if not self.args.get("sle_id"):
|
||||
self.update_outgoing_rate_on_transaction(sle)
|
||||
|
||||
def validate_negative_stock(self, sle):
|
||||
"""
|
||||
@@ -673,11 +675,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:
|
||||
|
||||
@@ -57,7 +57,7 @@ $.extend(shopping_cart, {
|
||||
callback: function(r) {
|
||||
d.hide();
|
||||
if (!r.exc) {
|
||||
$(".cart-tax-items").html(r.message.taxes);
|
||||
$(".cart-tax-items").html(r.message.total);
|
||||
shopping_cart.parent.find(
|
||||
`.address-container[data-address-type="${address_type}"]`
|
||||
).html(r.message.address);
|
||||
@@ -214,12 +214,15 @@ $.extend(shopping_cart, {
|
||||
},
|
||||
|
||||
place_order: function(btn) {
|
||||
shopping_cart.freeze();
|
||||
|
||||
return frappe.call({
|
||||
type: "POST",
|
||||
method: "erpnext.e_commerce.shopping_cart.cart.place_order",
|
||||
btn: btn,
|
||||
callback: function(r) {
|
||||
if(r.exc) {
|
||||
shopping_cart.unfreeze();
|
||||
var msg = "";
|
||||
if(r._server_messages) {
|
||||
msg = JSON.parse(r._server_messages || []).join("<br>");
|
||||
@@ -230,7 +233,6 @@ $.extend(shopping_cart, {
|
||||
.html(msg || frappe._("Something went wrong!"))
|
||||
.toggle(true);
|
||||
} else {
|
||||
$('.cart-container table').hide();
|
||||
$(btn).hide();
|
||||
window.location.href = '/orders/' + encodeURIComponent(r.message);
|
||||
}
|
||||
@@ -239,12 +241,15 @@ $.extend(shopping_cart, {
|
||||
},
|
||||
|
||||
request_quotation: function(btn) {
|
||||
shopping_cart.freeze();
|
||||
|
||||
return frappe.call({
|
||||
type: "POST",
|
||||
method: "erpnext.e_commerce.shopping_cart.cart.request_for_quotation",
|
||||
btn: btn,
|
||||
callback: function(r) {
|
||||
if(r.exc) {
|
||||
shopping_cart.unfreeze();
|
||||
var msg = "";
|
||||
if(r._server_messages) {
|
||||
msg = JSON.parse(r._server_messages || []).join("<br>");
|
||||
@@ -255,7 +260,6 @@ $.extend(shopping_cart, {
|
||||
.html(msg || frappe._("Something went wrong!"))
|
||||
.toggle(true);
|
||||
} else {
|
||||
$('.cart-container table').hide();
|
||||
$(btn).hide();
|
||||
window.location.href = '/quotations/' + encodeURIComponent(r.message);
|
||||
}
|
||||
|
||||
10
erpnext/templates/includes/cart/cart_items_total.html
Normal file
10
erpnext/templates/includes/cart/cart_items_total.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!-- Total at the end of the cart items -->
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="text-left item-grand-total" colspan="1">
|
||||
{{ _("Total") }}
|
||||
</th>
|
||||
<th class="text-left item-grand-total totals" colspan="3">
|
||||
{{ doc.get_formatted("total") }}
|
||||
</th>
|
||||
</tr>
|
||||
@@ -1,62 +1,61 @@
|
||||
<!-- Payment -->
|
||||
<div class="mb-3 frappe-card p-5 payment-summary">
|
||||
<h6>
|
||||
{{ _("Payment Summary") }}
|
||||
</h6>
|
||||
<div class="card h-100">
|
||||
<div class="card-body p-0">
|
||||
<table class="table w-100">
|
||||
<tr>
|
||||
<td class="bill-label">{{ _("Net Total (") + frappe.utils.cstr(doc.items|len) + _(" Items)") }}</td>
|
||||
<td class="bill-content net-total text-right">{{ doc.get_formatted("net_total") }}</td>
|
||||
</tr>
|
||||
<h6>
|
||||
{{ _("Payment Summary") }}
|
||||
</h6>
|
||||
<div class="card h-100">
|
||||
<div class="card-body p-0">
|
||||
<table class="table w-100">
|
||||
<tr>
|
||||
{% set total_items = frappe.utils.cstr(frappe.utils.flt(doc.total_qty, 0)) %}
|
||||
<td class="bill-label">{{ _("Net Total (") + total_items + _(" Items)") }}</td>
|
||||
<td class="bill-content net-total text-right">{{ doc.get_formatted("net_total") }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- taxes -->
|
||||
{% for d in doc.taxes %}
|
||||
{% if d.base_tax_amount %}
|
||||
<tr>
|
||||
<td class="bill-label">
|
||||
{{ d.description }}
|
||||
</td>
|
||||
<td class="bill-content text-right">
|
||||
{{ d.get_formatted("base_tax_amount") }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
<!-- taxes -->
|
||||
{% for d in doc.taxes %}
|
||||
{% if d.base_tax_amount %}
|
||||
<tr>
|
||||
<td class="bill-label">
|
||||
{{ d.description }}
|
||||
</td>
|
||||
<td class="bill-content text-right">
|
||||
{{ d.get_formatted("base_tax_amount") }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<!-- TODO: Apply Coupon Dialog-->
|
||||
<!-- {% set show_coupon_code = cart_settings.show_apply_coupon_code_in_website and cart_settings.enable_checkout %}
|
||||
{% if show_coupon_code %}
|
||||
<button class="btn btn-coupon-code w-100 text-left">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" stroke="var(--gray-600)" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 15.6213C19 15.2235 19.158 14.842 19.4393 14.5607L20.9393 13.0607C21.5251 12.4749 21.5251 11.5251 20.9393 10.9393L19.4393 9.43934C19.158 9.15804 19 8.7765 19 8.37868V6.5C19 5.67157 18.3284 5 17.5 5H15.6213C15.2235 5 14.842 4.84196 14.5607 4.56066L13.0607 3.06066C12.4749 2.47487 11.5251 2.47487 10.9393 3.06066L9.43934 4.56066C9.15804 4.84196 8.7765 5 8.37868 5H6.5C5.67157 5 5 5.67157 5 6.5V8.37868C5 8.7765 4.84196 9.15804 4.56066 9.43934L3.06066 10.9393C2.47487 11.5251 2.47487 12.4749 3.06066 13.0607L4.56066 14.5607C4.84196 14.842 5 15.2235 5 15.6213V17.5C5 18.3284 5.67157 19 6.5 19H8.37868C8.7765 19 9.15804 19.158 9.43934 19.4393L10.9393 20.9393C11.5251 21.5251 12.4749 21.5251 13.0607 20.9393L14.5607 19.4393C14.842 19.158 15.2235 19 15.6213 19H17.5C18.3284 19 19 18.3284 19 17.5V15.6213Z" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15 9L9 15" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.5 9.5C10.5 10.0523 10.0523 10.5 9.5 10.5C8.94772 10.5 8.5 10.0523 8.5 9.5C8.5 8.94772 8.94772 8.5 9.5 8.5C10.0523 8.5 10.5 8.94772 10.5 9.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15.5 14.5C15.5 15.0523 15.0523 15.5 14.5 15.5C13.9477 15.5 13.5 15.0523 13.5 14.5C13.5 13.9477 13.9477 13.5 14.5 13.5C15.0523 13.5 15.5 13.9477 15.5 14.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<span class="ml-2">Apply Coupon</span>
|
||||
</button>
|
||||
{% endif %} -->
|
||||
<!-- TODO: Apply Coupon Dialog-->
|
||||
<!-- {% set show_coupon_code = cart_settings.show_apply_coupon_code_in_website and cart_settings.enable_checkout %}
|
||||
{% if show_coupon_code %}
|
||||
<button class="btn btn-coupon-code w-100 text-left">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" stroke="var(--gray-600)" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 15.6213C19 15.2235 19.158 14.842 19.4393 14.5607L20.9393 13.0607C21.5251 12.4749 21.5251 11.5251 20.9393 10.9393L19.4393 9.43934C19.158 9.15804 19 8.7765 19 8.37868V6.5C19 5.67157 18.3284 5 17.5 5H15.6213C15.2235 5 14.842 4.84196 14.5607 4.56066L13.0607 3.06066C12.4749 2.47487 11.5251 2.47487 10.9393 3.06066L9.43934 4.56066C9.15804 4.84196 8.7765 5 8.37868 5H6.5C5.67157 5 5 5.67157 5 6.5V8.37868C5 8.7765 4.84196 9.15804 4.56066 9.43934L3.06066 10.9393C2.47487 11.5251 2.47487 12.4749 3.06066 13.0607L4.56066 14.5607C4.84196 14.842 5 15.2235 5 15.6213V17.5C5 18.3284 5.67157 19 6.5 19H8.37868C8.7765 19 9.15804 19.158 9.43934 19.4393L10.9393 20.9393C11.5251 21.5251 12.4749 21.5251 13.0607 20.9393L14.5607 19.4393C14.842 19.158 15.2235 19 15.6213 19H17.5C18.3284 19 19 18.3284 19 17.5V15.6213Z" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15 9L9 15" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.5 9.5C10.5 10.0523 10.0523 10.5 9.5 10.5C8.94772 10.5 8.5 10.0523 8.5 9.5C8.5 8.94772 8.94772 8.5 9.5 8.5C10.0523 8.5 10.5 8.94772 10.5 9.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15.5 14.5C15.5 15.0523 15.0523 15.5 14.5 15.5C13.9477 15.5 13.5 15.0523 13.5 14.5C13.5 13.9477 13.9477 13.5 14.5 13.5C15.0523 13.5 15.5 13.9477 15.5 14.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<span class="ml-2">Apply Coupon</span>
|
||||
</button>
|
||||
{% endif %} -->
|
||||
|
||||
<table class="table w-100 grand-total mt-6">
|
||||
<tr>
|
||||
<td class="bill-content net-total">{{ _("Grand Total") }}</td>
|
||||
<td class="bill-content net-total text-right">{{ doc.get_formatted("grand_total") }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="table w-100 grand-total mt-6">
|
||||
<tr>
|
||||
<td class="bill-content net-total">{{ _("Grand Total") }}</td>
|
||||
<td class="bill-content net-total text-right">{{ doc.get_formatted("grand_total") }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{% if cart_settings.enable_checkout %}
|
||||
<button class="btn btn-primary btn-place-order font-md w-100" type="button">
|
||||
{{ _('Place Order') }}
|
||||
</button>
|
||||
{% else %}
|
||||
<button class="btn btn-primary btn-request-for-quotation font-md w-100" type="button">
|
||||
{{ _('Request for Quote') }}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if cart_settings.enable_checkout %}
|
||||
<button class="btn btn-primary btn-place-order font-md w-100" type="button">
|
||||
{{ _('Place Order') }}
|
||||
</button>
|
||||
{% else %}
|
||||
<button class="btn btn-primary btn-request-for-quotation font-md w-100" type="button">
|
||||
{{ _('Request for Quote') }}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user