Compare commits
35 Commits
v12.27.0
...
version-12
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8be179c860 | ||
|
|
0b36799e8f | ||
|
|
1ee62a8342 | ||
|
|
74748c03e2 | ||
|
|
73484ef1bc | ||
|
|
1c25d7c6f2 | ||
|
|
432210d13d | ||
|
|
836bfc603a | ||
|
|
2d7c678a9d | ||
|
|
a92c53dd2b | ||
|
|
68fd8a1dcf | ||
|
|
5b2b76c4a7 | ||
|
|
c8780b0062 | ||
|
|
f531d1a69b | ||
|
|
51aa55f3b0 | ||
|
|
db0e3d3fbe | ||
|
|
ccffcf2596 | ||
|
|
c74a6f1827 | ||
|
|
73243c8ac3 | ||
|
|
1800ce2780 | ||
|
|
e5ea16a6dd | ||
|
|
753adf3ce4 | ||
|
|
fbbf29e829 | ||
|
|
ce2aa767b2 | ||
|
|
c9927efcae | ||
|
|
e9dbb46a06 | ||
|
|
39125a78e0 | ||
|
|
2a00164059 | ||
|
|
7d1953bb3b | ||
|
|
33eeb64fec | ||
|
|
abb4d99ca8 | ||
|
|
67f17c7a0c | ||
|
|
d09ed0a578 | ||
|
|
9b83e3856a | ||
|
|
656686f2b1 |
@@ -16,11 +16,11 @@ jobs:
|
||||
include:
|
||||
- name: "Python 2.7 Server Side Test"
|
||||
python: 2.7
|
||||
script: bench --site test_site run-tests --app erpnext --coverage
|
||||
script: bench --site test_site run-tests --app erpnext
|
||||
|
||||
- name: "Python 3.6 Server Side Test"
|
||||
python: 3.6
|
||||
script: bench --site test_site run-tests --app erpnext --coverage
|
||||
script: bench --site test_site run-tests --app erpnext
|
||||
|
||||
- name: "Python 2.7 Patch Test"
|
||||
python: 2.7
|
||||
@@ -74,8 +74,3 @@ install:
|
||||
- bench get-app erpnext $TRAVIS_BUILD_DIR
|
||||
- bench start &
|
||||
- bench --site test_site reinstall --yes
|
||||
|
||||
after_script:
|
||||
- pip install coverage==4.5.4
|
||||
- pip install python-coveralls
|
||||
- coveralls -b apps/erpnext -d ../../sites/.coverage
|
||||
|
||||
@@ -44,6 +44,8 @@ GNU/General Public License (see [license.txt](license.txt))
|
||||
|
||||
The ERPNext code is licensed as GNU General Public License (v3) and the Documentation is licensed as Creative Commons (CC-BY-SA-3.0) and the copyright is owned by Frappe Technologies Pvt Ltd (Frappe) and Contributors.
|
||||
|
||||
By contributing to ERPNext, you agree that your contributions will be licensed under its GNU General Public License (v3).
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe, unittest
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.utils import now_datetime
|
||||
|
||||
from erpnext.accounts.doctype.fiscal_year.fiscal_year import FiscalYearIncorrectDate
|
||||
|
||||
test_records = frappe.get_test_records('Fiscal Year')
|
||||
test_ignore = ["Company"]
|
||||
|
||||
class TestFiscalYear(unittest.TestCase):
|
||||
@@ -23,3 +25,29 @@ class TestFiscalYear(unittest.TestCase):
|
||||
})
|
||||
|
||||
self.assertRaises(FiscalYearIncorrectDate, fy.insert)
|
||||
|
||||
|
||||
def test_record_generator():
|
||||
test_records = [
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Short Fiscal Year 2011",
|
||||
"is_short_year": 1,
|
||||
"year_end_date": "2011-04-01",
|
||||
"year_start_date": "2011-12-31"
|
||||
}
|
||||
]
|
||||
|
||||
start = 2012
|
||||
end = now_datetime().year + 5
|
||||
for year in range(start, end):
|
||||
test_records.append({
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year {}".format(year),
|
||||
"year_start_date": "{}-01-01".format(year),
|
||||
"year_end_date": "{}-12-31".format(year)
|
||||
})
|
||||
|
||||
return test_records
|
||||
|
||||
test_records = test_record_generator()
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
[
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2012",
|
||||
"year_end_date": "2012-12-31",
|
||||
"year_start_date": "2012-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2013",
|
||||
"year_end_date": "2013-12-31",
|
||||
"year_start_date": "2013-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2014",
|
||||
"year_end_date": "2014-12-31",
|
||||
"year_start_date": "2014-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2015",
|
||||
"year_end_date": "2015-12-31",
|
||||
"year_start_date": "2015-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2016",
|
||||
"year_end_date": "2016-12-31",
|
||||
"year_start_date": "2016-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2017",
|
||||
"year_end_date": "2017-12-31",
|
||||
"year_start_date": "2017-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2018",
|
||||
"year_end_date": "2018-12-31",
|
||||
"year_start_date": "2018-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2019",
|
||||
"year_end_date": "2019-12-31",
|
||||
"year_start_date": "2019-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2020",
|
||||
"year_end_date": "2020-12-31",
|
||||
"year_start_date": "2020-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2021",
|
||||
"year_end_date": "2021-12-31",
|
||||
"year_start_date": "2021-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Short Fiscal Year 2021",
|
||||
"is_short_year": 1,
|
||||
"year_end_date": "2021-12-31",
|
||||
"year_start_date": "2021-04-01"
|
||||
}
|
||||
]
|
||||
@@ -247,8 +247,15 @@ class PurchaseInvoice(BuyingController):
|
||||
else:
|
||||
item.expense_account = stock_not_billed_account
|
||||
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
|
||||
item.expense_account = get_asset_category_account('fixed_asset_account', item=item.item_code,
|
||||
asset_category_account = get_asset_category_account('fixed_asset_account', item=item.item_code,
|
||||
company = self.company)
|
||||
if not asset_category_account:
|
||||
form_link = get_link_to_form('Asset Category', asset_category)
|
||||
throw(
|
||||
_("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company),
|
||||
title=_("Missing Account")
|
||||
)
|
||||
item.expense_account = asset_category_account
|
||||
elif item.is_fixed_asset and item.pr_detail:
|
||||
item.expense_account = asset_received_but_not_billed
|
||||
elif not item.expense_account and for_validate:
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%}
|
||||
{%- set einvoice = json.loads(doc.signed_einvoice) -%}
|
||||
|
||||
<div class="page-break">
|
||||
{% if doc.signed_einvoice %}
|
||||
{%- set einvoice = json.loads(doc.signed_einvoice) -%}
|
||||
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
|
||||
{% if letter_head and not no_letterhead %}
|
||||
<div class="letter-head">{{ letter_head }}</div>
|
||||
@@ -163,4 +164,10 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center" style="color: #98A1A9; font-size: 14px;">
|
||||
You must generate IRN before you can preview GST E-Invoice.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ frappe.ui.form.on('Asset', {
|
||||
frappe.ui.form.trigger("Asset", "is_existing_asset");
|
||||
frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1);
|
||||
frm.events.make_schedules_editable(frm);
|
||||
frm.trigger("toggle_make_depreciation_entry");
|
||||
|
||||
if (frm.doc.docstatus==1) {
|
||||
if (in_list(["Submitted", "Partially Depreciated", "Fully Depreciated"], frm.doc.status)) {
|
||||
@@ -141,6 +142,18 @@ frappe.ui.form.on('Asset', {
|
||||
}
|
||||
},
|
||||
|
||||
toggle_make_depreciation_entry: function(frm) {
|
||||
if (frm.doc.calculate_depreciation){
|
||||
if (in_list(["Submitted", "Partially Depreciated"], frm.doc.status)){
|
||||
frm.fields_dict['schedules'].grid.set_column_disp('make_depreciation_entry', true);
|
||||
} else {
|
||||
frm.fields_dict['schedules'].grid.set_column_disp('make_depreciation_entry', false);
|
||||
}
|
||||
|
||||
frm.refresh_field('schedules');
|
||||
}
|
||||
},
|
||||
|
||||
toggle_reference_doc: function(frm) {
|
||||
if (frm.doc.purchase_receipt && frm.doc.purchase_invoice && frm.doc.docstatus === 1) {
|
||||
frm.set_df_property('purchase_invoice', 'read_only', 1);
|
||||
|
||||
@@ -34,6 +34,8 @@ def make_depreciation_entry(asset_name, date=None):
|
||||
date = today()
|
||||
|
||||
asset = frappe.get_doc("Asset", asset_name)
|
||||
validate_asset(asset)
|
||||
|
||||
fixed_asset_account, accumulated_depreciation_account, depreciation_expense_account = \
|
||||
get_depreciation_accounts(asset)
|
||||
|
||||
@@ -59,7 +61,7 @@ def make_depreciation_entry(asset_name, date=None):
|
||||
"credit_in_account_currency": d.depreciation_amount,
|
||||
"reference_type": "Asset",
|
||||
"reference_name": asset.name,
|
||||
"cost_center": ""
|
||||
"cost_center": depreciation_cost_center
|
||||
}
|
||||
|
||||
debit_entry = {
|
||||
@@ -101,6 +103,10 @@ def make_depreciation_entry(asset_name, date=None):
|
||||
|
||||
return asset
|
||||
|
||||
def validate_asset(asset):
|
||||
if asset.status not in ['Submitted', 'Partially Depreciated']:
|
||||
frappe.throw(_("Cannot depreciate {0} Asset").format(asset.status))
|
||||
|
||||
def get_depreciation_accounts(asset):
|
||||
fixed_asset_account = accumulated_depreciation_account = depreciation_expense_account = None
|
||||
|
||||
|
||||
@@ -226,7 +226,7 @@ def new_bank_transaction(transaction):
|
||||
try:
|
||||
tags += transaction["category"]
|
||||
tags += ["Plaid Cat. {}".format(transaction["category_id"])]
|
||||
except KeyError:
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
|
||||
if not frappe.db.exists("Bank Transaction", dict(transaction_id=transaction["transaction_id"])):
|
||||
@@ -273,4 +273,4 @@ def automatic_synchronization():
|
||||
@frappe.whitelist()
|
||||
def get_link_token_for_update(access_token):
|
||||
plaid = PlaidConnector(access_token)
|
||||
return plaid.get_link_token(update_mode=True)
|
||||
return plaid.get_link_token(update_mode=True)
|
||||
|
||||
@@ -303,11 +303,11 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
if self.salary_structure:
|
||||
self.calculate_component_amounts("deductions")
|
||||
|
||||
|
||||
self.set_loan_repayment()
|
||||
self.set_component_amounts_based_on_payment_days()
|
||||
self.set_net_pay()
|
||||
|
||||
|
||||
def set_net_pay(self):
|
||||
self.total_deduction = self.get_component_totals("deductions")
|
||||
self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
|
||||
@@ -519,7 +519,7 @@ class SalarySlip(TransactionBase):
|
||||
# Total taxable earnings including additional and other incomes
|
||||
total_taxable_earnings = previous_taxable_earnings + current_structured_taxable_earnings + future_structured_taxable_earnings \
|
||||
+ current_additional_earnings + other_incomes + unclaimed_taxable_benefits - total_exemption_amount
|
||||
|
||||
|
||||
# Total taxable earnings without additional earnings with full tax
|
||||
total_taxable_earnings_without_full_tax_addl_components = total_taxable_earnings - current_additional_earnings_with_full_tax
|
||||
|
||||
@@ -527,7 +527,7 @@ class SalarySlip(TransactionBase):
|
||||
total_structured_tax_amount = self.calculate_tax_by_tax_slab(
|
||||
total_taxable_earnings_without_full_tax_addl_components, tax_slab)
|
||||
current_structured_tax_amount = (total_structured_tax_amount - previous_total_paid_taxes) / remaining_sub_periods
|
||||
|
||||
|
||||
# Total taxable earnings with additional earnings with full tax
|
||||
full_tax_on_additional_earnings = 0.0
|
||||
if current_additional_earnings_with_full_tax:
|
||||
@@ -563,7 +563,7 @@ class SalarySlip(TransactionBase):
|
||||
select sum(sd.amount)
|
||||
from
|
||||
`tabSalary Detail` sd join `tabSalary Slip` ss on sd.parent=ss.name
|
||||
where
|
||||
where
|
||||
sd.parentfield='earnings'
|
||||
and sd.is_tax_applicable=1
|
||||
and is_flexible_benefit=0
|
||||
@@ -676,9 +676,11 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
def get_amount_based_on_payment_days(self, row, joining_date, relieving_date):
|
||||
amount, additional_amount = row.amount, row.additional_amount
|
||||
timesheet_component = frappe.db.get_value("Salary Structure", self.salary_structure, "salary_component")
|
||||
|
||||
if (self.salary_structure and
|
||||
cint(row.depends_on_payment_days) and cint(self.total_working_days) and
|
||||
(not self.salary_slip_based_on_timesheet or
|
||||
cint(row.depends_on_payment_days) and cint(self.total_working_days)
|
||||
and (row.salary_component != timesheet_component or
|
||||
getdate(self.start_date) < joining_date or
|
||||
(relieving_date and getdate(self.end_date) > relieving_date)
|
||||
)):
|
||||
@@ -687,14 +689,14 @@ class SalarySlip(TransactionBase):
|
||||
amount = flt((flt(row.default_amount) * flt(self.payment_days)
|
||||
/ cint(self.total_working_days)), row.precision("amount")) + additional_amount
|
||||
|
||||
elif not self.payment_days and not self.salary_slip_based_on_timesheet and cint(row.depends_on_payment_days):
|
||||
elif not self.payment_days and row.salary_component != timesheet_component and cint(row.depends_on_payment_days):
|
||||
amount, additional_amount = 0, 0
|
||||
elif not row.amount:
|
||||
amount = flt(row.default_amount) + flt(row.additional_amount)
|
||||
|
||||
# apply rounding
|
||||
if frappe.get_cached_value("Salary Component", row.salary_component, "round_to_the_nearest_integer"):
|
||||
amount, additional_amount = rounded(amount), rounded(additional_amount)
|
||||
amount, additional_amount = rounded(amount or 0), rounded(additional_amount or 0)
|
||||
|
||||
return amount, additional_amount
|
||||
|
||||
@@ -782,7 +784,7 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
if flt(d.max_taxable_income) and flt(d.max_taxable_income) < annual_taxable_earning:
|
||||
continue
|
||||
|
||||
|
||||
tax_amount += tax_amount * flt(d.percent) / 100
|
||||
|
||||
return tax_amount
|
||||
|
||||
@@ -115,6 +115,41 @@ class TestSalarySlip(unittest.TestCase):
|
||||
frappe.db.set_value("Employee", frappe.get_value("Employee",
|
||||
{"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
|
||||
|
||||
def test_payment_days_in_salary_slip_based_on_timesheet(self):
|
||||
from erpnext.projects.doctype.timesheet.test_timesheet import (
|
||||
make_salary_structure_for_timesheet,
|
||||
make_timesheet,
|
||||
)
|
||||
from erpnext.projects.doctype.timesheet.timesheet import (
|
||||
make_salary_slip as make_salary_slip_for_timesheet,
|
||||
)
|
||||
|
||||
# Holidays included in working days
|
||||
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
|
||||
|
||||
emp = make_employee("test_employee_timesheet1@salary.com", company=erpnext.get_default_company())
|
||||
frappe.db.set_value("Employee", emp, {"relieving_date": None, "status": "Active"})
|
||||
|
||||
# salary structure based on timesheet
|
||||
make_salary_structure_for_timesheet(emp)
|
||||
timesheet = make_timesheet(emp, simulate=True)
|
||||
salary_slip = make_salary_slip_for_timesheet(timesheet.name)
|
||||
salary_slip.start_date = get_first_day(nowdate())
|
||||
salary_slip.end_date = get_last_day(nowdate())
|
||||
salary_slip.save()
|
||||
salary_slip.submit()
|
||||
|
||||
no_of_days = self.get_no_of_days()
|
||||
days_in_month = no_of_days[0]
|
||||
no_of_holidays = no_of_days[1]
|
||||
|
||||
self.assertEqual(salary_slip.payment_days, days_in_month - no_of_holidays)
|
||||
|
||||
# gross pay calculation based on attendance (payment days)
|
||||
gross_pay = 78100 - ((78000 / (days_in_month - no_of_holidays)) * flt(salary_slip.leave_without_pay))
|
||||
|
||||
self.assertEqual(salary_slip.gross_pay, flt(gross_pay, 2))
|
||||
|
||||
def test_employee_salary_slip_read_permission(self):
|
||||
make_employee("test_employee@salary.com")
|
||||
|
||||
@@ -175,7 +210,7 @@ class TestSalarySlip(unittest.TestCase):
|
||||
# as per assigned salary structure 40500 in monthly salary so 236000*5/100/12
|
||||
frappe.db.sql("""delete from `tabPayroll Period`""")
|
||||
frappe.db.sql("""delete from `tabSalary Component`""")
|
||||
|
||||
|
||||
payroll_period = create_payroll_period()
|
||||
|
||||
create_tax_slab(payroll_period, allow_tax_exemption=True)
|
||||
|
||||
@@ -138,6 +138,8 @@ def update_employee(employee, details, date=None, cancel=False):
|
||||
new_data = getdate(new_data)
|
||||
elif fieldtype =="Datetime" and new_data:
|
||||
new_data = get_datetime(new_data)
|
||||
elif fieldtype in ["Currency", "Float"] and new_data:
|
||||
new_data = flt(new_data)
|
||||
setattr(employee, item.fieldname, new_data)
|
||||
if item.fieldname in ["department", "designation", "branch"]:
|
||||
internal_work_history[item.fieldname] = item.new
|
||||
|
||||
@@ -20,10 +20,6 @@ class TestTimesheet(unittest.TestCase):
|
||||
for dt in ["Salary Slip", "Salary Structure", "Salary Structure Assignment", "Timesheet"]:
|
||||
frappe.db.sql("delete from `tab%s`" % dt)
|
||||
|
||||
if not frappe.db.exists("Salary Component", "Timesheet Component"):
|
||||
frappe.get_doc({"doctype": "Salary Component", "salary_component": "Timesheet Component"}).insert()
|
||||
|
||||
|
||||
def test_timesheet_billing_amount(self):
|
||||
make_salary_structure_for_timesheet("_T-Employee-00001")
|
||||
timesheet = make_timesheet("_T-Employee-00001", simulate=True, billable=1)
|
||||
@@ -177,6 +173,9 @@ def make_salary_structure_for_timesheet(employee):
|
||||
salary_structure_name = "Timesheet Salary Structure Test"
|
||||
frequency = "Monthly"
|
||||
|
||||
if not frappe.db.exists("Salary Component", "Timesheet Component"):
|
||||
frappe.get_doc({"doctype": "Salary Component", "salary_component": "Timesheet Component"}).insert()
|
||||
|
||||
salary_structure = make_salary_structure(salary_structure_name, frequency, dont_submit=True)
|
||||
salary_structure.salary_component = "Timesheet Component"
|
||||
salary_structure.salary_slip_based_on_timesheet = 1
|
||||
|
||||
@@ -940,7 +940,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
$.each(this.frm.doc.taxes || [], function(i, d) {
|
||||
if(d.charge_type == "Actual") {
|
||||
frappe.model.set_value(d.doctype, d.name, "tax_amount",
|
||||
flt(d.tax_amount) / flt(exchange_rate));
|
||||
flt(d.base_tax_amount) / flt(exchange_rate));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
"section_break_2",
|
||||
"sandbox_mode",
|
||||
"credentials",
|
||||
"advanced_settings_section",
|
||||
"client_id",
|
||||
"column_break_8",
|
||||
"client_secret",
|
||||
"auth_token",
|
||||
"token_expiry"
|
||||
],
|
||||
@@ -48,12 +52,32 @@
|
||||
"fieldname": "sandbox_mode",
|
||||
"fieldtype": "Check",
|
||||
"label": "Sandbox Mode"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "advanced_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Advanced Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "client_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "Client ID"
|
||||
},
|
||||
{
|
||||
"fieldname": "client_secret",
|
||||
"fieldtype": "Password",
|
||||
"label": "Client Secret"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_8",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-13 12:04:49.449199",
|
||||
"modified": "2021-11-16 19:50:28.029517",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Regional",
|
||||
"name": "E Invoice Settings",
|
||||
|
||||
@@ -23,9 +23,5 @@
|
||||
"StateCesAmt": "{item.state_cess_amount}",
|
||||
"StateCesNonAdvlAmt": "{item.state_cess_nadv_amount}",
|
||||
"OthChrg": "{item.other_charges}",
|
||||
"TotItemVal": "{item.total_value}",
|
||||
"BchDtls": {{
|
||||
"Nm": "{item.batch_no}",
|
||||
"ExpDt": "{item.batch_expiry_date}"
|
||||
}}
|
||||
"TotItemVal": "{item.total_value}"
|
||||
}}
|
||||
@@ -36,6 +36,7 @@ def validate_eligibility(doc):
|
||||
return False
|
||||
|
||||
invalid_company = not frappe.db.get_value('E Invoice User', { 'company': doc.get('company') })
|
||||
invalid_company_gstin = not frappe.db.get_value('E Invoice User', {'gstin': doc.get('company_gstin')})
|
||||
invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export']
|
||||
company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin')
|
||||
|
||||
@@ -44,7 +45,7 @@ def validate_eligibility(doc):
|
||||
no_taxes_applied = not doc.get('taxes') and not doc.get('gst_category') == 'Overseas'
|
||||
has_non_gst_item = any(d for d in doc.get('items', []) if d.get('is_non_gst'))
|
||||
|
||||
if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied or has_non_gst_item:
|
||||
if invalid_company or invalid_company_gstin or invalid_supply_type or company_transaction or no_taxes_applied or has_non_gst_item:
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -200,8 +201,6 @@ def get_item_list(invoice):
|
||||
item.taxable_value = abs(item.taxable_value)
|
||||
item.discount_amount = 0
|
||||
|
||||
item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
|
||||
item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
|
||||
item.is_service_item = 'Y' if item.gst_hsn_code and item.gst_hsn_code[:2] == "99" else 'N'
|
||||
item.serial_no = ""
|
||||
|
||||
@@ -308,7 +307,7 @@ def update_other_charges(tax_row, invoice_value_details, gst_accounts_list, invo
|
||||
|
||||
def get_payment_details(invoice):
|
||||
payee_name = invoice.company
|
||||
mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments])
|
||||
mode_of_payment = ""
|
||||
paid_amount = invoice.base_paid_amount
|
||||
outstanding_amount = invoice.outstanding_amount
|
||||
|
||||
@@ -459,10 +458,16 @@ def make_einvoice(invoice):
|
||||
try:
|
||||
einvoice = safe_json_load(einvoice)
|
||||
einvoice = santize_einvoice_fields(einvoice)
|
||||
except json.JSONDecodeError:
|
||||
raise
|
||||
except Exception:
|
||||
show_link_to_error_log(invoice, einvoice)
|
||||
|
||||
validate_totals(einvoice)
|
||||
try:
|
||||
validate_totals(einvoice)
|
||||
except Exception:
|
||||
log_error(einvoice)
|
||||
raise
|
||||
|
||||
return einvoice
|
||||
|
||||
@@ -534,7 +539,14 @@ def safe_json_load(json_string):
|
||||
pos = e.pos
|
||||
start, end = max(0, pos-20), min(len(json_string)-1, pos+20)
|
||||
snippet = json_string[start:end]
|
||||
frappe.throw(_("Error in input data. Please check for any special characters near following input: <br> {}").format(snippet))
|
||||
frappe.throw(
|
||||
_(
|
||||
"Error in input data. Please check for any special characters near following input: <br> {}"
|
||||
).format(snippet),
|
||||
title=_("Invalid JSON"),
|
||||
exc=e,
|
||||
)
|
||||
|
||||
|
||||
def throw_error_list(errors, title):
|
||||
if len(errors) > 1:
|
||||
@@ -611,10 +623,17 @@ class GSPConnector():
|
||||
request_log.save(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
|
||||
def get_client_credentials(self):
|
||||
if self.e_invoice_settings.client_id and self.e_invoice_settings.client_secret:
|
||||
return self.e_invoice_settings.client_id, self.e_invoice_settings.get_password('client_secret')
|
||||
|
||||
return frappe.conf.einvoice_client_id, frappe.conf.einvoice_client_secret
|
||||
|
||||
def fetch_auth_token(self):
|
||||
client_id, client_secret = self.get_client_credentials()
|
||||
headers = {
|
||||
'gspappid': frappe.conf.einvoice_client_id,
|
||||
'gspappsecret': frappe.conf.einvoice_client_secret
|
||||
'gspappid': client_id,
|
||||
'gspappsecret': client_secret
|
||||
}
|
||||
res = {}
|
||||
try:
|
||||
@@ -757,12 +776,13 @@ class GSPConnector():
|
||||
|
||||
headers = self.get_headers()
|
||||
eway_bill_details = get_eway_bill_details(args)
|
||||
|
||||
data = json.dumps({
|
||||
'Irn': args.irn,
|
||||
'Distance': cint(eway_bill_details.distance),
|
||||
'TransMode': eway_bill_details.mode_of_transport,
|
||||
'TransId': eway_bill_details.gstin,
|
||||
'TransName': eway_bill_details.transporter,
|
||||
'TransName': eway_bill_details.name,
|
||||
'TrnDocDt': eway_bill_details.document_date,
|
||||
'TrnDocNo': eway_bill_details.document_name,
|
||||
'VehNo': eway_bill_details.vehicle_no,
|
||||
@@ -854,7 +874,7 @@ class GSPConnector():
|
||||
if errors:
|
||||
throw_error_list(errors, title)
|
||||
else:
|
||||
link_to_error_list = '<a href="desk#List/Error Log/List?method=E Invoice Request Failed">Error Log</a>'
|
||||
link_to_error_list = '<a href="desk#List/Error Log/List?method=E Invoice Request Failed" target="_blank">Error Log</a>'
|
||||
frappe.msgprint(
|
||||
_('An error occurred while making e-invoicing request. Please check {} for more information.').format(link_to_error_list),
|
||||
title=title,
|
||||
|
||||
@@ -67,11 +67,11 @@ def validate_tax_category(doc, method):
|
||||
frappe.throw(_("Intra State tax category for GST State {0} already exists").format(doc.gst_state))
|
||||
|
||||
def update_gst_category(doc, method):
|
||||
if hasattr(doc, 'gst_category'):
|
||||
for link in doc.links:
|
||||
if link.link_doctype in ['Customer', 'Supplier']:
|
||||
if doc.get('gstin'):
|
||||
frappe.db.set_value(link.link_doctype, {'name': link.link_name, 'gst_category': 'Unregistered'}, 'gst_category', 'Registered Regular')
|
||||
for link in doc.links:
|
||||
if link.link_doctype in ['Customer', 'Supplier']:
|
||||
meta = frappe.get_meta(link.link_doctype)
|
||||
if doc.get('gstin') and meta.has_field('gst_category'):
|
||||
frappe.db.set_value(link.link_doctype, {'name': link.link_name, 'gst_category': 'Unregistered'}, 'gst_category', 'Registered Regular')
|
||||
|
||||
def set_gst_state_and_state_number(doc):
|
||||
if not doc.gst_state:
|
||||
|
||||
@@ -690,7 +690,6 @@ class Item(WebsiteGenerator):
|
||||
|
||||
def recalculate_bin_qty(self, new_name):
|
||||
from erpnext.stock.stock_balance import repost_stock
|
||||
frappe.db.auto_commit_on_many_writes = 1
|
||||
existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
|
||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
|
||||
|
||||
@@ -704,7 +703,6 @@ class Item(WebsiteGenerator):
|
||||
repost_stock(new_name, warehouse)
|
||||
|
||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock)
|
||||
frappe.db.auto_commit_on_many_writes = 0
|
||||
|
||||
def copy_specification_from_item_group(self):
|
||||
self.set("website_specifications", [])
|
||||
|
||||
@@ -32,64 +32,15 @@ class TestWarehouse(unittest.TestCase):
|
||||
self.assertEqual(p_warehouse.name, child_warehouse.parent_warehouse)
|
||||
self.assertEqual(child_warehouse.is_group, 0)
|
||||
|
||||
def test_warehouse_renaming(self):
|
||||
set_perpetual_inventory(1)
|
||||
create_warehouse("Test Warehouse for Renaming 1")
|
||||
account = get_inventory_account("_Test Company", "Test Warehouse for Renaming 1 - _TC")
|
||||
self.assertTrue(frappe.db.get_value("Warehouse", filters={"account": account}))
|
||||
def test_naming(self):
|
||||
company = "Wind Power LLC"
|
||||
warehouse_name = "Named Warehouse - WP"
|
||||
wh = frappe.get_doc(doctype="Warehouse", warehouse_name=warehouse_name, company=company).insert()
|
||||
self.assertEqual(wh.name, warehouse_name)
|
||||
|
||||
# Rename with abbr
|
||||
if frappe.db.exists("Warehouse", "Test Warehouse for Renaming 2 - _TC"):
|
||||
frappe.delete_doc("Warehouse", "Test Warehouse for Renaming 2 - _TC")
|
||||
rename_doc("Warehouse", "Test Warehouse for Renaming 1 - _TC", "Test Warehouse for Renaming 2 - _TC")
|
||||
|
||||
self.assertTrue(frappe.db.get_value("Warehouse",
|
||||
filters={"account": "Test Warehouse for Renaming 1 - _TC"}))
|
||||
|
||||
# Rename without abbr
|
||||
if frappe.db.exists("Warehouse", "Test Warehouse for Renaming 3 - _TC"):
|
||||
frappe.delete_doc("Warehouse", "Test Warehouse for Renaming 3 - _TC")
|
||||
|
||||
rename_doc("Warehouse", "Test Warehouse for Renaming 2 - _TC", "Test Warehouse for Renaming 3")
|
||||
|
||||
self.assertTrue(frappe.db.get_value("Warehouse",
|
||||
filters={"account": "Test Warehouse for Renaming 1 - _TC"}))
|
||||
|
||||
# Another rename with multiple dashes
|
||||
if frappe.db.exists("Warehouse", "Test - Warehouse - Company - _TC"):
|
||||
frappe.delete_doc("Warehouse", "Test - Warehouse - Company - _TC")
|
||||
rename_doc("Warehouse", "Test Warehouse for Renaming 3 - _TC", "Test - Warehouse - Company")
|
||||
|
||||
def test_warehouse_merging(self):
|
||||
set_perpetual_inventory(1)
|
||||
|
||||
create_warehouse("Test Warehouse for Merging 1")
|
||||
create_warehouse("Test Warehouse for Merging 2")
|
||||
|
||||
make_stock_entry(item_code="_Test Item", target="Test Warehouse for Merging 1 - _TC",
|
||||
qty=1, rate=100)
|
||||
make_stock_entry(item_code="_Test Item", target="Test Warehouse for Merging 2 - _TC",
|
||||
qty=1, rate=100)
|
||||
|
||||
existing_bin_qty = (
|
||||
cint(frappe.db.get_value("Bin",
|
||||
{"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 1 - _TC"}, "actual_qty"))
|
||||
+ cint(frappe.db.get_value("Bin",
|
||||
{"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 2 - _TC"}, "actual_qty"))
|
||||
)
|
||||
|
||||
rename_doc("Warehouse", "Test Warehouse for Merging 1 - _TC",
|
||||
"Test Warehouse for Merging 2 - _TC", merge=True)
|
||||
|
||||
self.assertFalse(frappe.db.exists("Warehouse", "Test Warehouse for Merging 1 - _TC"))
|
||||
|
||||
bin_qty = frappe.db.get_value("Bin",
|
||||
{"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 2 - _TC"}, "actual_qty")
|
||||
|
||||
self.assertEqual(bin_qty, existing_bin_qty)
|
||||
|
||||
self.assertTrue(frappe.db.get_value("Warehouse",
|
||||
filters={"account": "Test Warehouse for Merging 2 - _TC"}))
|
||||
warehouse_name = "Unnamed Warehouse"
|
||||
wh = frappe.get_doc(doctype="Warehouse", warehouse_name=warehouse_name, company=company).insert()
|
||||
self.assertIn(warehouse_name, wh.name)
|
||||
|
||||
def create_warehouse(warehouse_name, properties=None, company=None):
|
||||
if not company:
|
||||
@@ -145,4 +96,4 @@ def get_group_stock_account(company, company_abbr=None):
|
||||
if not company_abbr:
|
||||
company_abbr = frappe.get_cached_value("Company", company, 'abbr')
|
||||
group_stock_account = "Current Assets - " + company_abbr
|
||||
return group_stock_account
|
||||
return group_stock_account
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"creation": "2013-03-07 18:50:32",
|
||||
"description": "A logical Warehouse against which stock entries are made.",
|
||||
"doctype": "DocType",
|
||||
@@ -235,7 +234,7 @@
|
||||
"idx": 1,
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-03 18:41:52.442502",
|
||||
"modified": "2021-12-03 04:40:06.414630",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Warehouse",
|
||||
|
||||
@@ -7,6 +7,7 @@ from frappe.utils import cint, flt
|
||||
from frappe import throw, _
|
||||
from collections import defaultdict
|
||||
from frappe.utils.nestedset import NestedSet
|
||||
|
||||
from erpnext.stock import get_warehouse_account
|
||||
from frappe.contacts.address_and_contact import load_address_and_contact
|
||||
|
||||
@@ -18,8 +19,9 @@ class Warehouse(NestedSet):
|
||||
suffix = " - " + frappe.get_cached_value('Company', self.company, "abbr")
|
||||
if not self.warehouse_name.endswith(suffix):
|
||||
self.name = self.warehouse_name + suffix
|
||||
else:
|
||||
self.name = self.warehouse_name
|
||||
return
|
||||
|
||||
self.name = self.warehouse_name
|
||||
|
||||
def onload(self):
|
||||
'''load account name for General Ledger Report'''
|
||||
@@ -63,57 +65,6 @@ class Warehouse(NestedSet):
|
||||
return frappe.db.sql("""select name from `tabWarehouse`
|
||||
where parent_warehouse = %s limit 1""", self.name)
|
||||
|
||||
def before_rename(self, old_name, new_name, merge=False):
|
||||
super(Warehouse, self).before_rename(old_name, new_name, merge)
|
||||
|
||||
# Add company abbr if not provided
|
||||
new_warehouse = erpnext.encode_company_abbr(new_name, self.company)
|
||||
|
||||
if merge:
|
||||
if not frappe.db.exists("Warehouse", new_warehouse):
|
||||
frappe.throw(_("Warehouse {0} does not exist").format(new_warehouse))
|
||||
|
||||
if self.company != frappe.db.get_value("Warehouse", new_warehouse, "company"):
|
||||
frappe.throw(_("Both Warehouse must belong to same Company"))
|
||||
|
||||
return new_warehouse
|
||||
|
||||
def after_rename(self, old_name, new_name, merge=False):
|
||||
super(Warehouse, self).after_rename(old_name, new_name, merge)
|
||||
|
||||
new_warehouse_name = self.get_new_warehouse_name_without_abbr(new_name)
|
||||
self.db_set("warehouse_name", new_warehouse_name)
|
||||
|
||||
if merge:
|
||||
self.recalculate_bin_qty(new_name)
|
||||
|
||||
def get_new_warehouse_name_without_abbr(self, name):
|
||||
company_abbr = frappe.get_cached_value('Company', self.company, "abbr")
|
||||
parts = name.rsplit(" - ", 1)
|
||||
|
||||
if parts[-1].lower() == company_abbr.lower():
|
||||
name = parts[0]
|
||||
|
||||
return name
|
||||
|
||||
def recalculate_bin_qty(self, new_name):
|
||||
from erpnext.stock.stock_balance import repost_stock
|
||||
frappe.db.auto_commit_on_many_writes = 1
|
||||
existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
|
||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
|
||||
|
||||
repost_stock_for_items = frappe.db.sql_list("""select distinct item_code
|
||||
from tabBin where warehouse=%s""", new_name)
|
||||
|
||||
# Delete all existing bins to avoid duplicate bins for the same item and warehouse
|
||||
frappe.db.sql("delete from `tabBin` where warehouse=%s", new_name)
|
||||
|
||||
for item_code in repost_stock_for_items:
|
||||
repost_stock(item_code, new_name)
|
||||
|
||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock)
|
||||
frappe.db.auto_commit_on_many_writes = 0
|
||||
|
||||
def convert_to_group_or_ledger(self):
|
||||
if self.is_group:
|
||||
self.convert_to_ledger()
|
||||
@@ -232,4 +183,4 @@ def get_warehouses_based_on_account(account, company=None):
|
||||
frappe.throw(_("Warehouse not found against the account {0}")
|
||||
.format(account))
|
||||
|
||||
return warehouses
|
||||
return warehouses
|
||||
|
||||
@@ -329,7 +329,7 @@ def get_basic_details(args, item, overwrite_warehouse=True):
|
||||
if not out[d[1]]:
|
||||
out[d[1]] = frappe.get_cached_value('Company', args.company, d[2]) if d[2] else None
|
||||
|
||||
for fieldname in ("item_name", "item_group", "barcodes", "brand", "stock_uom"):
|
||||
for fieldname in ("item_name", "item_group", "brand", "stock_uom"):
|
||||
out[fieldname] = item.get(fieldname)
|
||||
|
||||
if args.get("manufacturer"):
|
||||
|
||||
@@ -368,7 +368,7 @@ class update_entries_after(object):
|
||||
batch = self.stock_queue[index]
|
||||
if qty_to_pop >= batch[0]:
|
||||
# consume current batch
|
||||
qty_to_pop = qty_to_pop - batch[0]
|
||||
qty_to_pop = _round_off_if_near_zero(qty_to_pop - batch[0])
|
||||
self.stock_queue.pop(index)
|
||||
if not self.stock_queue and qty_to_pop:
|
||||
# stock finished, qty still remains to be withdrawn
|
||||
@@ -382,8 +382,8 @@ class update_entries_after(object):
|
||||
batch[0] = batch[0] - qty_to_pop
|
||||
qty_to_pop = 0
|
||||
|
||||
stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.stock_queue))
|
||||
stock_qty = sum((flt(batch[0]) for batch in self.stock_queue))
|
||||
stock_value = _round_off_if_near_zero(sum((flt(batch[0]) * flt(batch[1]) for batch in self.stock_queue)))
|
||||
stock_qty = _round_off_if_near_zero(sum((flt(batch[0]) for batch in self.stock_queue)))
|
||||
|
||||
if stock_qty:
|
||||
self.valuation_rate = stock_value / flt(stock_qty)
|
||||
@@ -549,3 +549,12 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
|
||||
frappe.throw(msg=msg, title=_("Valuation Rate Missing"))
|
||||
|
||||
return valuation_rate
|
||||
|
||||
def _round_off_if_near_zero(number, precision = 7):
|
||||
"""Rounds off the number to zero only if number is close to zero for decimal
|
||||
specified in precision. Precision defaults to 7.
|
||||
"""
|
||||
if abs(0.0 - flt(number)) < (1.0 / (10**precision)):
|
||||
return 0.0
|
||||
|
||||
return flt(number)
|
||||
|
||||
713
license.txt
713
license.txt
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ braintree==3.57.1
|
||||
# frappe # https://github.com/frappe/frappe is installed during bench-init
|
||||
gocardless-pro==1.11.0
|
||||
googlemaps==3.1.1
|
||||
pandas==0.24.2
|
||||
pandas>=0.24.0,<1.2.0
|
||||
plaid-python~=7.2.1
|
||||
PyGithub==1.44.1
|
||||
python-stdnum==1.12
|
||||
|
||||
Reference in New Issue
Block a user