Merge branch 'develop' into serial-no-space
This commit is contained in:
commit
7cbd755951
24
.github/workflows/backport.yml
vendored
24
.github/workflows/backport.yml
vendored
@ -1,16 +1,26 @@
|
||||
name: Backport
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
backport:
|
||||
runs-on: ubuntu-18.04
|
||||
name: Backport
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Backport
|
||||
uses: tibdex/backport@v1
|
||||
- name: Checkout Actions
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
repository: "frappe/backport"
|
||||
path: ./actions
|
||||
ref: develop
|
||||
- name: Install Actions
|
||||
run: npm install --production --prefix ./actions
|
||||
- name: Run backport
|
||||
uses: ./actions/backport
|
||||
with:
|
||||
token: ${{secrets.BACKPORT_BOT_TOKEN}}
|
||||
labelsToAdd: "backport"
|
||||
title: "{{originalTitle}}"
|
||||
|
1
.github/workflows/docs-checker.yml
vendored
1
.github/workflows/docs-checker.yml
vendored
@ -6,6 +6,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: 'Setup Environment'
|
||||
|
1
.github/workflows/patch.yml
vendored
1
.github/workflows/patch.yml
vendored
@ -5,6 +5,7 @@ on: [pull_request, workflow_dispatch]
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-18.04
|
||||
timeout-minutes: 60
|
||||
|
||||
name: Patch Test
|
||||
|
||||
|
1
.github/workflows/server-tests.yml
vendored
1
.github/workflows/server-tests.yml
vendored
@ -9,6 +9,7 @@ on:
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-18.04
|
||||
timeout-minutes: 60
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
1
.github/workflows/ui-tests.yml
vendored
1
.github/workflows/ui-tests.yml
vendored
@ -7,6 +7,7 @@ on:
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-18.04
|
||||
timeout-minutes: 60
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
12
CODEOWNERS
12
CODEOWNERS
@ -21,13 +21,13 @@ erpnext/quality_management/ @marination @rohitwaghchaure
|
||||
erpnext/shopping_cart/ @marination
|
||||
erpnext/stock/ @marination @rohitwaghchaure @ankush
|
||||
|
||||
erpnext/crm/ @ruchamahabal
|
||||
erpnext/education/ @ruchamahabal
|
||||
erpnext/healthcare/ @ruchamahabal
|
||||
erpnext/hr/ @ruchamahabal
|
||||
erpnext/crm/ @ruchamahabal @pateljannat
|
||||
erpnext/education/ @ruchamahabal @pateljannat
|
||||
erpnext/healthcare/ @ruchamahabal @pateljannat @chillaranand
|
||||
erpnext/hr/ @ruchamahabal @pateljannat
|
||||
erpnext/non_profit/ @ruchamahabal
|
||||
erpnext/payroll @ruchamahabal
|
||||
erpnext/projects/ @ruchamahabal
|
||||
erpnext/payroll @ruchamahabal @pateljannat
|
||||
erpnext/projects/ @ruchamahabal @pateljannat
|
||||
|
||||
erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
|
||||
|
||||
|
@ -5,7 +5,7 @@ import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
from frappe.utils import getdate
|
||||
|
||||
__version__ = '13.7.0'
|
||||
__version__ = '13.7.1'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
|
@ -27,6 +27,9 @@ class ExchangeRateRevaluation(Document):
|
||||
if not (self.company and self.posting_date):
|
||||
frappe.throw(_("Please select Company and Posting Date to getting entries"))
|
||||
|
||||
def on_cancel(self):
|
||||
self.ignore_linked_doctypes = ('GL Entry')
|
||||
|
||||
@frappe.whitelist()
|
||||
def check_journal_entry_condition(self):
|
||||
total_debit = frappe.db.get_value("Journal Entry Account", {
|
||||
@ -99,10 +102,12 @@ class ExchangeRateRevaluation(Document):
|
||||
sum(debit) - sum(credit) as balance
|
||||
from `tabGL Entry`
|
||||
where account in (%s)
|
||||
group by account, party_type, party
|
||||
and posting_date <= %s
|
||||
and is_cancelled = 0
|
||||
group by account, NULLIF(party_type,''), NULLIF(party,'')
|
||||
having sum(debit) != sum(credit)
|
||||
order by account
|
||||
""" % ', '.join(['%s']*len(accounts)), tuple(accounts), as_dict=1)
|
||||
""" % (', '.join(['%s']*len(accounts)), '%s'), tuple(accounts + [self.posting_date]), as_dict=1)
|
||||
|
||||
return account_details
|
||||
|
||||
@ -143,9 +148,9 @@ class ExchangeRateRevaluation(Document):
|
||||
"party_type": d.get("party_type"),
|
||||
"party": d.get("party"),
|
||||
"account_currency": d.get("account_currency"),
|
||||
"balance": d.get("balance_in_account_currency"),
|
||||
dr_or_cr: abs(d.get("balance_in_account_currency")),
|
||||
"exchange_rate":d.get("new_exchange_rate"),
|
||||
"balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")),
|
||||
dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")),
|
||||
"exchange_rate": flt(d.get("new_exchange_rate"), d.precision("new_exchange_rate")),
|
||||
"reference_type": "Exchange Rate Revaluation",
|
||||
"reference_name": self.name,
|
||||
})
|
||||
@ -154,9 +159,9 @@ class ExchangeRateRevaluation(Document):
|
||||
"party_type": d.get("party_type"),
|
||||
"party": d.get("party"),
|
||||
"account_currency": d.get("account_currency"),
|
||||
"balance": d.get("balance_in_account_currency"),
|
||||
reverse_dr_or_cr: abs(d.get("balance_in_account_currency")),
|
||||
"exchange_rate": d.get("current_exchange_rate"),
|
||||
"balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")),
|
||||
reverse_dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")),
|
||||
"exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")),
|
||||
"reference_type": "Exchange Rate Revaluation",
|
||||
"reference_name": self.name
|
||||
})
|
||||
@ -185,9 +190,9 @@ def get_account_details(account, company, posting_date, party_type=None, party=N
|
||||
|
||||
account_details = {}
|
||||
company_currency = erpnext.get_company_currency(company)
|
||||
balance = get_balance_on(account, party_type=party_type, party=party, in_account_currency=False)
|
||||
balance = get_balance_on(account, date=posting_date, party_type=party_type, party=party, in_account_currency=False)
|
||||
if balance:
|
||||
balance_in_account_currency = get_balance_on(account, party_type=party_type, party=party)
|
||||
balance_in_account_currency = get_balance_on(account, date=posting_date, party_type=party_type, party=party)
|
||||
current_exchange_rate = balance / balance_in_account_currency if balance_in_account_currency else 0
|
||||
new_exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date)
|
||||
new_balance_in_base_currency = balance_in_account_currency * new_exchange_rate
|
||||
|
@ -27,7 +27,8 @@
|
||||
"base_tax_amount",
|
||||
"base_total",
|
||||
"base_tax_amount_after_discount_amount",
|
||||
"item_wise_tax_detail"
|
||||
"item_wise_tax_detail",
|
||||
"dont_recompute_tax"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -200,13 +201,22 @@
|
||||
"fieldname": "included_in_paid_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Considered In Paid Amount"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "dont_recompute_tax",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Dont Recompute tax",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-14 01:44:36.899147",
|
||||
"modified": "2021-07-27 12:40:59.051803",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Taxes and Charges",
|
||||
|
@ -99,7 +99,6 @@ class ReceivablePayableReport(object):
|
||||
voucher_no = gle.voucher_no,
|
||||
party = gle.party,
|
||||
posting_date = gle.posting_date,
|
||||
remarks = gle.remarks,
|
||||
account_currency = gle.account_currency,
|
||||
invoiced = 0.0,
|
||||
paid = 0.0,
|
||||
@ -579,7 +578,7 @@ class ReceivablePayableReport(object):
|
||||
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, remarks, {0}
|
||||
against_voucher_type, against_voucher, account_currency, {0}
|
||||
from
|
||||
`tabGL Entry`
|
||||
where
|
||||
@ -792,8 +791,6 @@ class ReceivablePayableReport(object):
|
||||
self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link',
|
||||
options='Supplier Group')
|
||||
|
||||
self.add_column(label=_('Remarks'), fieldname='remarks', fieldtype='Text', width=200)
|
||||
|
||||
def add_column(self, label, fieldname=None, fieldtype='Currency', options=None, width=120):
|
||||
if not fieldname: fieldname = scrub(label)
|
||||
if fieldtype=='Currency': options='currency'
|
||||
|
@ -966,7 +966,7 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle, precision):
|
||||
for e in existing_gle:
|
||||
if entry.account == e.account:
|
||||
account_existed = True
|
||||
if (entry.account == e.account and entry.against_account == e.against_account
|
||||
if (entry.account == e.account
|
||||
and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center)
|
||||
and ( flt(entry.debit, precision) != flt(e.debit, precision) or
|
||||
flt(entry.credit, precision) != flt(e.credit, precision))):
|
||||
|
@ -1112,8 +1112,11 @@ class AccountsController(TransactionBase):
|
||||
for d in self.get("payment_schedule"):
|
||||
if d.invoice_portion:
|
||||
d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
||||
d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
||||
d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount'))
|
||||
d.outstanding = d.payment_amount
|
||||
elif not d.invoice_portion:
|
||||
d.base_payment_amount = flt(base_grand_total * self.get("conversion_rate"), d.precision('base_payment_amount'))
|
||||
|
||||
|
||||
def set_due_date(self):
|
||||
due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]
|
||||
|
127
erpnext/controllers/employee_boarding_controller.py
Normal file
127
erpnext/controllers/employee_boarding_controller.py
Normal file
@ -0,0 +1,127 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.desk.form import assign_to
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt, unique
|
||||
|
||||
class EmployeeBoardingController(Document):
|
||||
'''
|
||||
Create the project and the task for the boarding process
|
||||
Assign to the concerned person and roles as per the onboarding/separation template
|
||||
'''
|
||||
def validate(self):
|
||||
# remove the task if linked before submitting the form
|
||||
if self.amended_from:
|
||||
for activity in self.activities:
|
||||
activity.task = ''
|
||||
|
||||
def on_submit(self):
|
||||
# create the project for the given employee onboarding
|
||||
project_name = _(self.doctype) + ' : '
|
||||
if self.doctype == 'Employee Onboarding':
|
||||
project_name += self.job_applicant
|
||||
else:
|
||||
project_name += self.employee
|
||||
|
||||
project = frappe.get_doc({
|
||||
'doctype': 'Project',
|
||||
'project_name': project_name,
|
||||
'expected_start_date': self.date_of_joining if self.doctype == 'Employee Onboarding' else self.resignation_letter_date,
|
||||
'department': self.department,
|
||||
'company': self.company
|
||||
}).insert(ignore_permissions=True, ignore_mandatory=True)
|
||||
|
||||
self.db_set('project', project.name)
|
||||
self.db_set('boarding_status', 'Pending')
|
||||
self.reload()
|
||||
self.create_task_and_notify_user()
|
||||
|
||||
def create_task_and_notify_user(self):
|
||||
# create the task for the given project and assign to the concerned person
|
||||
for activity in self.activities:
|
||||
if activity.task:
|
||||
continue
|
||||
|
||||
task = frappe.get_doc({
|
||||
'doctype': 'Task',
|
||||
'project': self.project,
|
||||
'subject': activity.activity_name + ' : ' + self.employee_name,
|
||||
'description': activity.description,
|
||||
'department': self.department,
|
||||
'company': self.company,
|
||||
'task_weight': activity.task_weight
|
||||
}).insert(ignore_permissions=True)
|
||||
activity.db_set('task', task.name)
|
||||
|
||||
users = [activity.user] if activity.user else []
|
||||
if activity.role:
|
||||
user_list = frappe.db.sql_list('''
|
||||
SELECT
|
||||
DISTINCT(has_role.parent)
|
||||
FROM
|
||||
`tabHas Role` has_role
|
||||
LEFT JOIN `tabUser` user
|
||||
ON has_role.parent = user.name
|
||||
WHERE
|
||||
has_role.parenttype = 'User'
|
||||
AND user.enabled = 1
|
||||
AND has_role.role = %s
|
||||
''', activity.role)
|
||||
users = unique(users + user_list)
|
||||
|
||||
if 'Administrator' in users:
|
||||
users.remove('Administrator')
|
||||
|
||||
# assign the task the users
|
||||
if users:
|
||||
self.assign_task_to_users(task, users)
|
||||
|
||||
def assign_task_to_users(self, task, users):
|
||||
for user in users:
|
||||
args = {
|
||||
'assign_to': [user],
|
||||
'doctype': task.doctype,
|
||||
'name': task.name,
|
||||
'description': task.description or task.subject,
|
||||
'notify': self.notify_users_by_email
|
||||
}
|
||||
assign_to.add(args)
|
||||
|
||||
def on_cancel(self):
|
||||
# delete task project
|
||||
for task in frappe.get_all('Task', filters={'project': self.project}):
|
||||
frappe.delete_doc('Task', task.name, force=1)
|
||||
frappe.delete_doc('Project', self.project, force=1)
|
||||
self.db_set('project', '')
|
||||
for activity in self.activities:
|
||||
activity.db_set('task', '')
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_onboarding_details(parent, parenttype):
|
||||
return frappe.get_all('Employee Boarding Activity',
|
||||
fields=['activity_name', 'role', 'user', 'required_for_employee_creation', 'description', 'task_weight'],
|
||||
filters={'parent': parent, 'parenttype': parenttype},
|
||||
order_by= 'idx')
|
||||
|
||||
|
||||
def update_employee_boarding_status(project):
|
||||
employee_onboarding = frappe.db.exists('Employee Onboarding', {'project': project.name})
|
||||
employee_separation = frappe.db.exists('Employee Separation', {'project': project.name})
|
||||
|
||||
if not (employee_onboarding or employee_separation):
|
||||
return
|
||||
|
||||
status = 'Pending'
|
||||
if flt(project.percent_complete) > 0.0 and flt(project.percent_complete) < 100.0:
|
||||
status = 'In Process'
|
||||
elif flt(project.percent_complete) == 100.0:
|
||||
status = 'Completed'
|
||||
|
||||
if employee_onboarding:
|
||||
frappe.db.set_value('Employee Onboarding', employee_onboarding, 'boarding_status', status)
|
||||
elif employee_separation:
|
||||
frappe.db.set_value('Employee Separation', employee_separation, 'boarding_status', status)
|
@ -54,12 +54,17 @@ class StockController(AccountsController):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
for d in self.get("items"):
|
||||
if hasattr(d, 'serial_no') and hasattr(d, 'batch_no') and d.serial_no and d.batch_no:
|
||||
serial_nos = get_serial_nos(d.serial_no)
|
||||
for serial_no_data in frappe.get_all("Serial No",
|
||||
filters={"name": ("in", serial_nos)}, fields=["batch_no", "name"]):
|
||||
if serial_no_data.batch_no != d.batch_no:
|
||||
serial_nos = frappe.get_all("Serial No",
|
||||
fields=["batch_no", "name", "warehouse"],
|
||||
filters={
|
||||
"name": ("in", get_serial_nos(d.serial_no))
|
||||
}
|
||||
)
|
||||
|
||||
for row in serial_nos:
|
||||
if row.warehouse and row.batch_no != d.batch_no:
|
||||
frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}")
|
||||
.format(d.idx, serial_no_data.name, d.batch_no))
|
||||
.format(d.idx, row.name, d.batch_no))
|
||||
|
||||
if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2:
|
||||
expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
|
||||
|
@ -152,7 +152,7 @@ class calculate_taxes_and_totals(object):
|
||||
validate_taxes_and_charges(tax)
|
||||
validate_inclusive_tax(tax, self.doc)
|
||||
|
||||
if not self.doc.get('is_consolidated'):
|
||||
if not (self.doc.get('is_consolidated') or tax.get("dont_recompute_tax")):
|
||||
tax.item_wise_tax_detail = {}
|
||||
|
||||
tax_fields = ["total", "tax_amount_after_discount_amount",
|
||||
@ -347,7 +347,7 @@ class calculate_taxes_and_totals(object):
|
||||
elif tax.charge_type == "On Item Quantity":
|
||||
current_tax_amount = tax_rate * item.qty
|
||||
|
||||
if not self.doc.get("is_consolidated"):
|
||||
if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
|
||||
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
|
||||
|
||||
return current_tax_amount
|
||||
@ -455,7 +455,8 @@ class calculate_taxes_and_totals(object):
|
||||
def _cleanup(self):
|
||||
if not self.doc.get('is_consolidated'):
|
||||
for tax in self.doc.get("taxes"):
|
||||
tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
|
||||
if not tax.get("dont_recompute_tax"):
|
||||
tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
|
||||
|
||||
def set_discount_amount(self):
|
||||
if self.doc.additional_discount_percentage:
|
||||
|
0
erpnext/crm/doctype/campaign/__init__.py
Normal file
0
erpnext/crm/doctype/campaign/__init__.py
Normal file
17
erpnext/crm/doctype/campaign/campaign.js
Normal file
17
erpnext/crm/doctype/campaign/campaign.js
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Campaign', {
|
||||
refresh: function(frm) {
|
||||
erpnext.toggle_naming_series();
|
||||
|
||||
if (frm.doc.__islocal) {
|
||||
frm.toggle_display("naming_series", frappe.boot.sysdefaults.campaign_naming_by=="Naming Series");
|
||||
} else {
|
||||
cur_frm.add_custom_button(__("View Leads"), function() {
|
||||
frappe.route_options = {"source": "Campaign", "campaign_name": frm.doc.name};
|
||||
frappe.set_route("List", "Lead");
|
||||
}, "fa fa-list", true);
|
||||
}
|
||||
}
|
||||
});
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "naming_series:",
|
||||
@ -39,17 +40,9 @@
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Text",
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"fieldname": "description_section",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldname": "campaign_schedules_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Campaign Schedules"
|
||||
},
|
||||
{
|
||||
"fieldname": "campaign_schedules",
|
||||
@ -58,16 +51,25 @@
|
||||
"options": "Campaign Email Schedule"
|
||||
},
|
||||
{
|
||||
"fieldname": "campaign_schedules_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Campaign Schedules"
|
||||
"fieldname": "description_section",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Text",
|
||||
"width": "300px"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-bullhorn",
|
||||
"idx": 1,
|
||||
"modified": "2019-07-22 12:03:39.832342",
|
||||
"links": [],
|
||||
"modified": "2021-06-30 18:05:06.412712",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"module": "CRM",
|
||||
"name": "Campaign",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
@ -1,9 +1,7 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
# 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.model.document import Document
|
||||
from frappe.model.naming import set_name_by_naming_series
|
||||
|
8
erpnext/crm/doctype/campaign/test_campaign.py
Normal file
8
erpnext/crm/doctype/campaign/test_campaign.py
Normal file
@ -0,0 +1,8 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestCampaign(unittest.TestCase):
|
||||
pass
|
@ -25,7 +25,8 @@ doctype_js = {
|
||||
"Address": "public/js/address.js",
|
||||
"Communication": "public/js/communication.js",
|
||||
"Event": "public/js/event.js",
|
||||
"Newsletter": "public/js/newsletter.js"
|
||||
"Newsletter": "public/js/newsletter.js",
|
||||
"Contact": "public/js/contact.js"
|
||||
}
|
||||
|
||||
override_doctype_class = {
|
||||
|
@ -9,7 +9,7 @@ from frappe.utils import flt, getdate
|
||||
from frappe import _
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.model.document import Document
|
||||
from erpnext.hr.utils import set_employee_name
|
||||
from erpnext.hr.utils import set_employee_name, validate_active_employee
|
||||
|
||||
class Appraisal(Document):
|
||||
def validate(self):
|
||||
@ -19,6 +19,7 @@ class Appraisal(Document):
|
||||
if not self.goals:
|
||||
frappe.throw(_("Goals cannot be empty"))
|
||||
|
||||
validate_active_employee(self.employee)
|
||||
set_employee_name(self)
|
||||
self.validate_dates()
|
||||
self.validate_existing_appraisal()
|
||||
|
@ -8,11 +8,13 @@ from frappe.utils import getdate, nowdate
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cstr, get_datetime, formatdate
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
|
||||
class Attendance(Document):
|
||||
def validate(self):
|
||||
from erpnext.controllers.status_updater import validate_status
|
||||
validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"])
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_attendance_date()
|
||||
self.validate_duplicate_record()
|
||||
self.validate_employee_status()
|
||||
|
@ -8,10 +8,11 @@ from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import date_diff, add_days, getdate
|
||||
from erpnext.hr.doctype.employee.employee import is_holiday
|
||||
from erpnext.hr.utils import validate_dates
|
||||
from erpnext.hr.utils import validate_dates, validate_active_employee
|
||||
|
||||
class AttendanceRequest(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
validate_dates(self, self.from_date, self.to_date)
|
||||
if self.half_day:
|
||||
if not getdate(self.from_date)<=getdate(self.half_day_date)<=getdate(self.to_date):
|
||||
|
@ -7,12 +7,13 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import date_diff, add_days, getdate, cint, format_date
|
||||
from frappe.model.document import Document
|
||||
from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \
|
||||
from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, validate_active_employee, \
|
||||
get_holidays_for_employee, create_additional_leave_ledger_entry
|
||||
|
||||
class CompensatoryLeaveRequest(Document):
|
||||
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
validate_dates(self, self.work_from_date, self.work_end_date)
|
||||
if self.half_day:
|
||||
if not self.half_day_date:
|
||||
|
@ -13,8 +13,10 @@ from frappe.model.document import Document
|
||||
from erpnext.utilities.transaction_base import delete_events
|
||||
from frappe.utils.nestedset import NestedSet
|
||||
|
||||
class EmployeeUserDisabledError(frappe.ValidationError): pass
|
||||
class EmployeeLeftValidationError(frappe.ValidationError): pass
|
||||
class EmployeeUserDisabledError(frappe.ValidationError):
|
||||
pass
|
||||
class InactiveEmployeeStatusError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
class Employee(NestedSet):
|
||||
nsm_parent_field = 'reports_to'
|
||||
@ -196,7 +198,7 @@ class Employee(NestedSet):
|
||||
message += "<br><br><ul><li>" + "</li><li>".join(link_to_employees)
|
||||
message += "</li></ul><br>"
|
||||
message += _("Please make sure the employees above report to another Active employee.")
|
||||
throw(message, EmployeeLeftValidationError, _("Cannot Relieve Employee"))
|
||||
throw(message, InactiveEmployeeStatusError, _("Cannot Relieve Employee"))
|
||||
if not self.relieving_date:
|
||||
throw(_("Please enter relieving date."))
|
||||
|
||||
|
@ -7,7 +7,7 @@ import frappe
|
||||
import erpnext
|
||||
import unittest
|
||||
import frappe.utils
|
||||
from erpnext.hr.doctype.employee.employee import EmployeeLeftValidationError
|
||||
from erpnext.hr.doctype.employee.employee import InactiveEmployeeStatusError
|
||||
|
||||
test_records = frappe.get_test_records('Employee')
|
||||
|
||||
@ -45,10 +45,33 @@ class TestEmployee(unittest.TestCase):
|
||||
employee2_doc.save()
|
||||
employee1_doc.reload()
|
||||
employee1_doc.status = 'Left'
|
||||
self.assertRaises(EmployeeLeftValidationError, employee1_doc.save)
|
||||
self.assertRaises(InactiveEmployeeStatusError, employee1_doc.save)
|
||||
|
||||
def test_employee_status_inactive(self):
|
||||
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
|
||||
from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
|
||||
from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
|
||||
|
||||
employee = make_employee("test_employee_status@company.com")
|
||||
employee_doc = frappe.get_doc("Employee", employee)
|
||||
employee_doc.status = "Inactive"
|
||||
employee_doc.save()
|
||||
employee_doc.reload()
|
||||
|
||||
make_holiday_list()
|
||||
frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List")
|
||||
|
||||
frappe.db.sql("""delete from `tabSalary Structure` where name='Test Inactive Employee Salary Slip'""")
|
||||
salary_structure = make_salary_structure("Test Inactive Employee Salary Slip", "Monthly",
|
||||
employee=employee_doc.name, company=employee_doc.company)
|
||||
salary_slip = make_salary_slip(salary_structure.name, employee=employee_doc.name)
|
||||
|
||||
self.assertRaises(InactiveEmployeeStatusError, salary_slip.save)
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def make_employee(user, company=None, **kwargs):
|
||||
""
|
||||
if not frappe.db.get_value("User", user):
|
||||
frappe.get_doc({
|
||||
"doctype": "User",
|
||||
@ -80,4 +103,5 @@ def make_employee(user, company=None, **kwargs):
|
||||
employee.insert()
|
||||
return employee.name
|
||||
else:
|
||||
frappe.db.set_value("Employee", {"employee_name":user}, "status", "Active")
|
||||
return frappe.get_value("Employee", {"employee_name":user}, "name")
|
||||
|
@ -8,6 +8,7 @@ from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt, nowdate
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
|
||||
class EmployeeAdvanceOverPayment(frappe.ValidationError):
|
||||
pass
|
||||
@ -18,11 +19,11 @@ class EmployeeAdvance(Document):
|
||||
'make_payment_via_journal_entry')
|
||||
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
self.set_status()
|
||||
|
||||
def on_cancel(self):
|
||||
self.ignore_linked_doctypes = ('GL Entry')
|
||||
self.set_status()
|
||||
|
||||
def set_status(self):
|
||||
if self.docstatus == 0:
|
||||
@ -183,9 +184,9 @@ def make_return_entry(employee, company, employee_advance_name, return_amount,
|
||||
bank_cash_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
|
||||
if not bank_cash_account:
|
||||
frappe.throw(_("Please set a Default Cash Account in Company defaults"))
|
||||
|
||||
|
||||
advance_account_currency = frappe.db.get_value('Account', advance_account, 'account_currency')
|
||||
|
||||
|
||||
je = frappe.new_doc('Journal Entry')
|
||||
je.posting_date = nowdate()
|
||||
je.voucher_type = get_voucher_type(mode_of_payment)
|
||||
@ -229,4 +230,4 @@ def get_voucher_type(mode_of_payment=None):
|
||||
if mode_of_payment_type == "Bank":
|
||||
voucher_type = "Bank Entry"
|
||||
|
||||
return voucher_type
|
||||
return voucher_type
|
||||
|
@ -9,9 +9,11 @@ from frappe.model.document import Document
|
||||
from frappe import _
|
||||
|
||||
from erpnext.hr.doctype.shift_assignment.shift_assignment import get_actual_start_end_datetime_of_shift
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
|
||||
class EmployeeCheckin(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_duplicate_log()
|
||||
self.fetch_shift()
|
||||
|
||||
@ -122,7 +124,7 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki
|
||||
def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
|
||||
"""Given a set of logs in chronological order calculates the total working hours based on the parameters.
|
||||
Zero is returned for all invalid cases.
|
||||
|
||||
|
||||
:param logs: The List of 'Employee Checkin'.
|
||||
:param check_in_out_type: One of: 'Alternating entries as IN and OUT during the same shift', 'Strictly based on Log Type in Employee Checkin'
|
||||
:param working_hours_calc_type: One of: 'First Check-in and Last Check-out', 'Every Valid Check-in and Check-out'
|
||||
|
@ -50,28 +50,13 @@ frappe.ui.form.on('Employee Onboarding', {
|
||||
}, __('Create'));
|
||||
frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||
}
|
||||
if (frm.doc.docstatus === 1 && frm.doc.project) {
|
||||
frappe.call({
|
||||
method: "erpnext.hr.utils.get_boarding_status",
|
||||
args: {
|
||||
"project": frm.doc.project
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value('boarding_status', r.message);
|
||||
}
|
||||
refresh_field("boarding_status");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
employee_onboarding_template: function(frm) {
|
||||
frm.set_value("activities" ,"");
|
||||
if (frm.doc.employee_onboarding_template) {
|
||||
frappe.call({
|
||||
method: "erpnext.hr.utils.get_onboarding_details",
|
||||
method: "erpnext.controllers.employee_boarding_controller.get_onboarding_details",
|
||||
args: {
|
||||
"parent": frm.doc.employee_onboarding_template,
|
||||
"parenttype": "Employee Onboarding Template"
|
||||
|
@ -30,18 +30,14 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Job Applicant",
|
||||
"options": "Job Applicant",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "job_offer",
|
||||
"fieldtype": "Link",
|
||||
"label": "Job Offer",
|
||||
"options": "Job Offer",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "job_applicant.applicant_name",
|
||||
@ -49,116 +45,90 @@
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Employee Name",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "employee",
|
||||
"fieldtype": "Link",
|
||||
"label": "Employee",
|
||||
"options": "Employee",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "date_of_joining",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Date of Joining",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Date of Joining"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "Pending",
|
||||
"fieldname": "boarding_status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"options": "\nPending\nIn Process\nCompleted",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Pending\nIn Process\nCompleted",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "0",
|
||||
"fieldname": "notify_users_by_email",
|
||||
"fieldtype": "Check",
|
||||
"label": "Notify users by email",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Notify users by email"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_7",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "employee_onboarding_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Employee Onboarding Template",
|
||||
"options": "Employee Onboarding Template",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Employee Onboarding Template"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"fieldname": "department",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Department",
|
||||
"options": "Department",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Department"
|
||||
},
|
||||
{
|
||||
"fieldname": "designation",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Designation",
|
||||
"options": "Designation",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Designation"
|
||||
},
|
||||
{
|
||||
"fieldname": "employee_grade",
|
||||
"fieldtype": "Link",
|
||||
"label": "Employee Grade",
|
||||
"options": "Employee Grade",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Employee Grade"
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "table_for_activity",
|
||||
"fieldtype": "Section Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "activities",
|
||||
"fieldtype": "Table",
|
||||
"label": "Activities",
|
||||
"options": "Employee Boarding Activity",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Employee Boarding Activity"
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
@ -167,14 +137,12 @@
|
||||
"no_copy": 1,
|
||||
"options": "Employee Onboarding",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-25 15:22:24.923835",
|
||||
"modified": "2021-06-03 18:01:51.097927",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee Onboarding",
|
||||
|
@ -5,7 +5,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from erpnext.hr.utils import EmployeeBoardingController
|
||||
from erpnext.controllers.employee_boarding_controller import EmployeeBoardingController
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
class IncompleteTaskError(frappe.ValidationError): pass
|
||||
@ -16,9 +16,9 @@ class EmployeeOnboarding(EmployeeBoardingController):
|
||||
self.validate_duplicate_employee_onboarding()
|
||||
|
||||
def validate_duplicate_employee_onboarding(self):
|
||||
emp_onboarding = frappe.db.exists("Employee Onboarding",{"job_applicant": self.job_applicant})
|
||||
emp_onboarding = frappe.db.exists("Employee Onboarding", {"job_applicant": self.job_applicant})
|
||||
if emp_onboarding and emp_onboarding != self.name:
|
||||
frappe.throw(_("Employee Onboarding: {0} is already for Job Applicant: {1}").format(frappe.bold(emp_onboarding), frappe.bold(self.job_applicant)))
|
||||
frappe.throw(_("Employee Onboarding: {0} already exists for Job Applicant: {1}").format(frappe.bold(emp_onboarding), frappe.bold(self.job_applicant)))
|
||||
|
||||
def validate_employee_creation(self):
|
||||
if self.docstatus != 1:
|
||||
@ -30,7 +30,7 @@ class EmployeeOnboarding(EmployeeBoardingController):
|
||||
else:
|
||||
task_status = frappe.db.get_value("Task", activity.task, "status")
|
||||
if task_status not in ["Completed", "Cancelled"]:
|
||||
frappe.throw(_("All the mandatory Task for employee creation hasn't been done yet."), IncompleteTaskError)
|
||||
frappe.throw(_("All the mandatory tasks for employee creation are not completed yet."), IncompleteTaskError)
|
||||
|
||||
def on_submit(self):
|
||||
super(EmployeeOnboarding, self).on_submit()
|
||||
|
@ -11,39 +11,26 @@ from erpnext.hr.doctype.employee_onboarding.employee_onboarding import Incomplet
|
||||
from erpnext.hr.doctype.job_offer.test_job_offer import create_job_offer
|
||||
|
||||
class TestEmployeeOnboarding(unittest.TestCase):
|
||||
def test_employee_onboarding_incomplete_task(self):
|
||||
def setUp(self):
|
||||
if frappe.db.exists('Employee Onboarding', {'employee_name': 'Test Researcher'}):
|
||||
frappe.delete_doc('Employee Onboarding', {'employee_name': 'Test Researcher'})
|
||||
_set_up()
|
||||
applicant = get_job_applicant()
|
||||
|
||||
job_offer = create_job_offer(job_applicant=applicant.name)
|
||||
job_offer.submit()
|
||||
project = "Employee Onboarding : Test Researcher - test@researcher.com"
|
||||
frappe.db.sql("delete from tabProject where name=%s", project)
|
||||
frappe.db.sql("delete from tabTask where project=%s", project)
|
||||
|
||||
onboarding = frappe.new_doc('Employee Onboarding')
|
||||
onboarding.job_applicant = applicant.name
|
||||
onboarding.job_offer = job_offer.name
|
||||
onboarding.company = '_Test Company'
|
||||
onboarding.designation = 'Researcher'
|
||||
onboarding.append('activities', {
|
||||
'activity_name': 'Assign ID Card',
|
||||
'role': 'HR User',
|
||||
'required_for_employee_creation': 1
|
||||
})
|
||||
onboarding.append('activities', {
|
||||
'activity_name': 'Assign a laptop',
|
||||
'role': 'HR User'
|
||||
})
|
||||
onboarding.status = 'Pending'
|
||||
onboarding.insert()
|
||||
onboarding.submit()
|
||||
def test_employee_onboarding_incomplete_task(self):
|
||||
onboarding = create_employee_onboarding()
|
||||
|
||||
project_name = frappe.db.get_value("Project", onboarding.project, "project_name")
|
||||
project_name = frappe.db.get_value('Project', onboarding.project, 'project_name')
|
||||
self.assertEqual(project_name, 'Employee Onboarding : Test Researcher - test@researcher.com')
|
||||
|
||||
# don't allow making employee if onboarding is not complete
|
||||
self.assertRaises(IncompleteTaskError, make_employee, onboarding.name)
|
||||
|
||||
# boarding status
|
||||
self.assertEqual(onboarding.boarding_status, 'Pending')
|
||||
|
||||
# complete the task
|
||||
project = frappe.get_doc('Project', onboarding.project)
|
||||
for task in frappe.get_all('Task', dict(project=project.name)):
|
||||
@ -51,6 +38,10 @@ class TestEmployeeOnboarding(unittest.TestCase):
|
||||
task.status = 'Completed'
|
||||
task.save()
|
||||
|
||||
# boarding status
|
||||
onboarding.reload()
|
||||
self.assertEqual(onboarding.boarding_status, 'Completed')
|
||||
|
||||
# make employee
|
||||
onboarding.reload()
|
||||
employee = make_employee(onboarding.name)
|
||||
@ -61,6 +52,13 @@ class TestEmployeeOnboarding(unittest.TestCase):
|
||||
employee.insert()
|
||||
self.assertEqual(employee.employee_name, 'Test Researcher')
|
||||
|
||||
def tearDown(self):
|
||||
for entry in frappe.get_all('Employee Onboarding'):
|
||||
doc = frappe.get_doc('Employee Onboarding', entry.name)
|
||||
doc.cancel()
|
||||
doc.delete()
|
||||
|
||||
|
||||
def get_job_applicant():
|
||||
if frappe.db.exists('Job Applicant', 'Test Researcher - test@researcher.com'):
|
||||
return frappe.get_doc('Job Applicant', 'Test Researcher - test@researcher.com')
|
||||
@ -72,10 +70,35 @@ def get_job_applicant():
|
||||
applicant.insert()
|
||||
return applicant
|
||||
|
||||
def _set_up():
|
||||
for doctype in ["Employee Onboarding"]:
|
||||
frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype))
|
||||
def get_job_offer(applicant_name):
|
||||
job_offer = frappe.db.exists('Job Offer', {'job_applicant': applicant_name})
|
||||
if job_offer:
|
||||
return frappe.get_doc('Job Offer', job_offer)
|
||||
|
||||
project = "Employee Onboarding : Test Researcher - test@researcher.com"
|
||||
frappe.db.sql("delete from tabProject where name=%s", project)
|
||||
frappe.db.sql("delete from tabTask where project=%s", project)
|
||||
job_offer = create_job_offer(job_applicant=applicant_name)
|
||||
job_offer.submit()
|
||||
return job_offer
|
||||
|
||||
def create_employee_onboarding():
|
||||
applicant = get_job_applicant()
|
||||
job_offer = get_job_offer(applicant.name)
|
||||
|
||||
onboarding = frappe.new_doc('Employee Onboarding')
|
||||
onboarding.job_applicant = applicant.name
|
||||
onboarding.job_offer = job_offer.name
|
||||
onboarding.company = '_Test Company'
|
||||
onboarding.designation = 'Researcher'
|
||||
onboarding.append('activities', {
|
||||
'activity_name': 'Assign ID Card',
|
||||
'role': 'HR User',
|
||||
'required_for_employee_creation': 1
|
||||
})
|
||||
onboarding.append('activities', {
|
||||
'activity_name': 'Assign a laptop',
|
||||
'role': 'HR User'
|
||||
})
|
||||
onboarding.status = 'Pending'
|
||||
onboarding.insert()
|
||||
onboarding.submit()
|
||||
|
||||
return onboarding
|
@ -7,12 +7,11 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import getdate
|
||||
from erpnext.hr.utils import update_employee
|
||||
from erpnext.hr.utils import update_employee, validate_active_employee
|
||||
|
||||
class EmployeePromotion(Document):
|
||||
def validate(self):
|
||||
if frappe.get_value("Employee", self.employee, "status") != "Active":
|
||||
frappe.throw(_("Cannot promote Employee with status Left or Inactive"))
|
||||
validate_active_employee(self.employee)
|
||||
|
||||
def before_submit(self):
|
||||
if getdate(self.promotion_date) > getdate():
|
||||
|
@ -7,9 +7,11 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import get_link_to_form
|
||||
from frappe.model.document import Document
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
|
||||
class EmployeeReferral(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.referrer)
|
||||
self.set_full_name()
|
||||
self.set_referral_bonus_payment_status()
|
||||
|
||||
|
@ -23,27 +23,13 @@ frappe.ui.form.on('Employee Separation', {
|
||||
frappe.set_route('List', 'Task', {project: frm.doc.project});
|
||||
},__("View"));
|
||||
}
|
||||
if (frm.doc.docstatus === 1 && frm.doc.project) {
|
||||
frappe.call({
|
||||
method: "erpnext.hr.utils.get_boarding_status",
|
||||
args: {
|
||||
"project": frm.doc.project
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value('boarding_status', r.message);
|
||||
}
|
||||
refresh_field("boarding_status");
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
employee_separation_template: function(frm) {
|
||||
frm.set_value("activities" ,"");
|
||||
if (frm.doc.employee_separation_template) {
|
||||
frappe.call({
|
||||
method: "erpnext.hr.utils.get_onboarding_details",
|
||||
method: "erpnext.controllers.employee_boarding_controller.get_onboarding_details",
|
||||
args: {
|
||||
"parent": frm.doc.employee_separation_template,
|
||||
"parenttype": "Employee Separation Template"
|
||||
|
@ -50,11 +50,12 @@
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "Pending",
|
||||
"fieldname": "boarding_status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"options": "\nPending\nIn Process\nCompleted",
|
||||
"reqd": 1
|
||||
"options": "Pending\nIn Process\nCompleted",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
@ -147,7 +148,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-28 15:58:36.020196",
|
||||
"modified": "2021-06-03 18:02:54.007313",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee Separation",
|
||||
|
@ -3,7 +3,7 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from erpnext.hr.utils import EmployeeBoardingController
|
||||
from erpnext.controllers.employee_boarding_controller import EmployeeBoardingController
|
||||
|
||||
class EmployeeSeparation(EmployeeBoardingController):
|
||||
def validate(self):
|
||||
|
@ -6,21 +6,43 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
test_dependencies = ["Employee Onboarding"]
|
||||
test_dependencies = ['Employee Onboarding']
|
||||
|
||||
class TestEmployeeSeparation(unittest.TestCase):
|
||||
def test_employee_separation(self):
|
||||
employee = frappe.db.get_value("Employee", {"status": "Active"})
|
||||
separation = frappe.new_doc('Employee Separation')
|
||||
separation.employee = employee
|
||||
separation.company = '_Test Company'
|
||||
separation.append('activities', {
|
||||
'activity_name': 'Deactivate Employee',
|
||||
'role': 'HR User'
|
||||
})
|
||||
separation.boarding_status = 'Pending'
|
||||
separation.insert()
|
||||
separation.submit()
|
||||
separation = create_employee_separation()
|
||||
|
||||
self.assertEqual(separation.docstatus, 1)
|
||||
self.assertEqual(separation.boarding_status, 'Pending')
|
||||
|
||||
project = frappe.get_doc('Project', separation.project)
|
||||
project.percent_complete_method = 'Manual'
|
||||
project.status = 'Completed'
|
||||
project.save()
|
||||
|
||||
separation.reload()
|
||||
self.assertEqual(separation.boarding_status, 'Completed')
|
||||
|
||||
separation.cancel()
|
||||
self.assertEqual(separation.project, "")
|
||||
self.assertEqual(separation.project, '')
|
||||
|
||||
def tearDown(self):
|
||||
for entry in frappe.get_all('Employee Separation'):
|
||||
doc = frappe.get_doc('Employee Separation', entry.name)
|
||||
if doc.docstatus == 1:
|
||||
doc.cancel()
|
||||
doc.delete()
|
||||
|
||||
def create_employee_separation():
|
||||
employee = frappe.db.get_value('Employee', {'status': 'Active'})
|
||||
separation = frappe.new_doc('Employee Separation')
|
||||
separation.employee = employee
|
||||
separation.company = '_Test Company'
|
||||
separation.append('activities', {
|
||||
'activity_name': 'Deactivate Employee',
|
||||
'role': 'HR User'
|
||||
})
|
||||
separation.boarding_status = 'Pending'
|
||||
separation.insert()
|
||||
separation.submit()
|
||||
return separation
|
@ -10,10 +10,6 @@ from frappe.utils import getdate
|
||||
from erpnext.hr.utils import update_employee
|
||||
|
||||
class EmployeeTransfer(Document):
|
||||
def validate(self):
|
||||
if frappe.get_value("Employee", self.employee, "status") != "Active":
|
||||
frappe.throw(_("Cannot transfer Employee with status Left or Inactive"))
|
||||
|
||||
def before_submit(self):
|
||||
if getdate(self.transfer_date) > getdate():
|
||||
frappe.throw(_("Employee Transfer cannot be submitted before Transfer Date"),
|
||||
|
@ -6,7 +6,7 @@ import frappe, erpnext
|
||||
from frappe import _
|
||||
from frappe.utils import get_fullname, flt, cstr, get_link_to_form
|
||||
from frappe.model.document import Document
|
||||
from erpnext.hr.utils import set_employee_name, share_doc_with_approver
|
||||
from erpnext.hr.utils import set_employee_name, share_doc_with_approver, validate_active_employee
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
|
||||
@ -23,6 +23,7 @@ class ExpenseClaim(AccountsController):
|
||||
'make_payment_via_journal_entry')
|
||||
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_advances()
|
||||
self.validate_sanctioned_amount()
|
||||
self.calculate_total_amount()
|
||||
@ -35,8 +36,8 @@ class ExpenseClaim(AccountsController):
|
||||
if self.task and not self.project:
|
||||
self.project = frappe.db.get_value("Task", self.task, "project")
|
||||
|
||||
def set_status(self):
|
||||
self.status = {
|
||||
def set_status(self, update=False):
|
||||
status = {
|
||||
"0": "Draft",
|
||||
"1": "Submitted",
|
||||
"2": "Cancelled"
|
||||
@ -44,14 +45,18 @@ class ExpenseClaim(AccountsController):
|
||||
|
||||
paid_amount = flt(self.total_amount_reimbursed) + flt(self.total_advance_amount)
|
||||
precision = self.precision("grand_total")
|
||||
if (self.is_paid or (flt(self.total_sanctioned_amount) > 0
|
||||
and flt(self.grand_total, precision) == flt(paid_amount, precision))) \
|
||||
and self.docstatus == 1 and self.approval_status == 'Approved':
|
||||
self.status = "Paid"
|
||||
if (self.is_paid or (flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1
|
||||
and flt(self.grand_total, precision) == flt(paid_amount, precision))) and self.approval_status == 'Approved':
|
||||
status = "Paid"
|
||||
elif flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1 and self.approval_status == 'Approved':
|
||||
self.status = "Unpaid"
|
||||
status = "Unpaid"
|
||||
elif self.docstatus == 1 and self.approval_status == 'Rejected':
|
||||
self.status = 'Rejected'
|
||||
status = 'Rejected'
|
||||
|
||||
if update:
|
||||
self.db_set("status", status)
|
||||
else:
|
||||
self.status = status
|
||||
|
||||
def on_update(self):
|
||||
share_doc_with_approver(self, self.expense_approver)
|
||||
@ -74,7 +79,7 @@ class ExpenseClaim(AccountsController):
|
||||
if self.is_paid:
|
||||
update_reimbursed_amount(self)
|
||||
|
||||
self.set_status()
|
||||
self.set_status(update=True)
|
||||
self.update_claimed_amount_in_employee_advance()
|
||||
|
||||
def on_cancel(self):
|
||||
@ -86,7 +91,6 @@ class ExpenseClaim(AccountsController):
|
||||
if self.is_paid:
|
||||
update_reimbursed_amount(self)
|
||||
|
||||
self.set_status()
|
||||
self.update_claimed_amount_in_employee_advance()
|
||||
|
||||
def update_claimed_amount_in_employee_advance(self):
|
||||
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, get_fullname, add_days, nowdate
|
||||
from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver
|
||||
from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver, validate_active_employee
|
||||
from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange
|
||||
@ -22,6 +22,7 @@ class LeaveApplication(Document):
|
||||
return _("{0}: From {0} of type {1}").format(self.employee_name, self.leave_type)
|
||||
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
set_employee_name(self)
|
||||
self.validate_dates()
|
||||
self.validate_balance_leaves()
|
||||
|
@ -7,7 +7,7 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import getdate, nowdate, flt
|
||||
from erpnext.hr.utils import set_employee_name
|
||||
from erpnext.hr.utils import set_employee_name, validate_active_employee
|
||||
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
|
||||
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
|
||||
from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves
|
||||
@ -15,6 +15,7 @@ from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leav
|
||||
class LeaveEncashment(Document):
|
||||
def validate(self):
|
||||
set_employee_name(self)
|
||||
validate_active_employee(self.employee)
|
||||
self.get_leave_details_for_encashment()
|
||||
self.validate_salary_structure()
|
||||
|
||||
|
@ -9,10 +9,12 @@ from frappe.model.document import Document
|
||||
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, now_datetime, nowdate
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
class ShiftAssignment(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_overlapping_dates()
|
||||
|
||||
if self.end_date and self.end_date <= self.start_date:
|
||||
|
@ -7,12 +7,13 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import formatdate, getdate
|
||||
from erpnext.hr.utils import share_doc_with_approver
|
||||
from erpnext.hr.utils import share_doc_with_approver, validate_active_employee
|
||||
|
||||
class OverlapError(frappe.ValidationError): pass
|
||||
|
||||
class ShiftRequest(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_dates()
|
||||
self.validate_shift_request_overlap_dates()
|
||||
self.validate_approver()
|
||||
|
@ -5,6 +5,8 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
|
||||
class TravelRequest(Document):
|
||||
pass
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
|
@ -3,128 +3,15 @@
|
||||
|
||||
import erpnext
|
||||
import frappe
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee, InactiveEmployeeStatusError
|
||||
from frappe import _
|
||||
from frappe.desk.form import assign_to
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import (add_days, cstr, flt, format_datetime, formatdate,
|
||||
get_datetime, getdate, nowdate, today, unique)
|
||||
|
||||
get_datetime, getdate, nowdate, today, unique, get_link_to_form)
|
||||
|
||||
class DuplicateDeclarationError(frappe.ValidationError): pass
|
||||
|
||||
|
||||
class EmployeeBoardingController(Document):
|
||||
'''
|
||||
Create the project and the task for the boarding process
|
||||
Assign to the concerned person and roles as per the onboarding/separation template
|
||||
'''
|
||||
def validate(self):
|
||||
# remove the task if linked before submitting the form
|
||||
if self.amended_from:
|
||||
for activity in self.activities:
|
||||
activity.task = ''
|
||||
|
||||
def on_submit(self):
|
||||
# create the project for the given employee onboarding
|
||||
project_name = _(self.doctype) + " : "
|
||||
if self.doctype == "Employee Onboarding":
|
||||
project_name += self.job_applicant
|
||||
else:
|
||||
project_name += self.employee
|
||||
|
||||
project = frappe.get_doc({
|
||||
"doctype": "Project",
|
||||
"project_name": project_name,
|
||||
"expected_start_date": self.date_of_joining if self.doctype == "Employee Onboarding" else self.resignation_letter_date,
|
||||
"department": self.department,
|
||||
"company": self.company
|
||||
}).insert(ignore_permissions=True, ignore_mandatory=True)
|
||||
|
||||
self.db_set("project", project.name)
|
||||
self.db_set("boarding_status", "Pending")
|
||||
self.reload()
|
||||
self.create_task_and_notify_user()
|
||||
|
||||
def create_task_and_notify_user(self):
|
||||
# create the task for the given project and assign to the concerned person
|
||||
for activity in self.activities:
|
||||
if activity.task:
|
||||
continue
|
||||
|
||||
task = frappe.get_doc({
|
||||
"doctype": "Task",
|
||||
"project": self.project,
|
||||
"subject": activity.activity_name + " : " + self.employee_name,
|
||||
"description": activity.description,
|
||||
"department": self.department,
|
||||
"company": self.company,
|
||||
"task_weight": activity.task_weight
|
||||
}).insert(ignore_permissions=True)
|
||||
activity.db_set("task", task.name)
|
||||
|
||||
users = [activity.user] if activity.user else []
|
||||
if activity.role:
|
||||
user_list = frappe.db.sql_list('''
|
||||
SELECT
|
||||
DISTINCT(has_role.parent)
|
||||
FROM
|
||||
`tabHas Role` has_role
|
||||
LEFT JOIN `tabUser` user
|
||||
ON has_role.parent = user.name
|
||||
WHERE
|
||||
has_role.parenttype = 'User'
|
||||
AND user.enabled = 1
|
||||
AND has_role.role = %s
|
||||
''', activity.role)
|
||||
users = unique(users + user_list)
|
||||
|
||||
if "Administrator" in users:
|
||||
users.remove("Administrator")
|
||||
|
||||
# assign the task the users
|
||||
if users:
|
||||
self.assign_task_to_users(task, users)
|
||||
|
||||
def assign_task_to_users(self, task, users):
|
||||
for user in users:
|
||||
args = {
|
||||
'assign_to': [user],
|
||||
'doctype': task.doctype,
|
||||
'name': task.name,
|
||||
'description': task.description or task.subject,
|
||||
'notify': self.notify_users_by_email
|
||||
}
|
||||
assign_to.add(args)
|
||||
|
||||
def on_cancel(self):
|
||||
# delete task project
|
||||
for task in frappe.get_all("Task", filters={"project": self.project}):
|
||||
frappe.delete_doc("Task", task.name, force=1)
|
||||
frappe.delete_doc("Project", self.project, force=1)
|
||||
self.db_set('project', '')
|
||||
for activity in self.activities:
|
||||
activity.db_set("task", "")
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_onboarding_details(parent, parenttype):
|
||||
return frappe.get_all("Employee Boarding Activity",
|
||||
fields=["activity_name", "role", "user", "required_for_employee_creation", "description", "task_weight"],
|
||||
filters={"parent": parent, "parenttype": parenttype},
|
||||
order_by= "idx")
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_boarding_status(project):
|
||||
status = 'Pending'
|
||||
if project:
|
||||
doc = frappe.get_doc('Project', project)
|
||||
if flt(doc.percent_complete) > 0.0 and flt(doc.percent_complete) < 100.0:
|
||||
status = 'In Process'
|
||||
elif flt(doc.percent_complete) == 100.0:
|
||||
status = 'Completed'
|
||||
return status
|
||||
|
||||
def set_employee_name(doc):
|
||||
if doc.employee and not doc.employee_name:
|
||||
doc.employee_name = frappe.db.get_value("Employee", doc.employee, "employee_name")
|
||||
@ -522,3 +409,8 @@ def share_doc_with_approver(doc, user):
|
||||
approver = approvers.get(doc.doctype)
|
||||
if doc_before_save.get(approver) != doc.get(approver):
|
||||
frappe.share.remove(doc.doctype, doc.name, doc_before_save.get(approver))
|
||||
|
||||
def validate_active_employee(employee):
|
||||
if frappe.db.get_value("Employee", employee, "status") == "Inactive":
|
||||
frappe.throw(_("Transactions cannot be created for an Inactive Employee {0}.").format(
|
||||
get_link_to_form("Employee", employee)), InactiveEmployeeStatusError)
|
@ -83,7 +83,7 @@ frappe.ui.form.on("BOM", {
|
||||
|
||||
if (!frm.doc.__islocal && frm.doc.docstatus<2) {
|
||||
frm.add_custom_button(__("Update Cost"), function() {
|
||||
frm.events.update_cost(frm);
|
||||
frm.events.update_cost(frm, true);
|
||||
});
|
||||
frm.add_custom_button(__("Browse BOM"), function() {
|
||||
frappe.route_options = {
|
||||
@ -318,14 +318,15 @@ frappe.ui.form.on("BOM", {
|
||||
})
|
||||
},
|
||||
|
||||
update_cost: function(frm) {
|
||||
update_cost: function(frm, save_doc=false) {
|
||||
return frappe.call({
|
||||
doc: frm.doc,
|
||||
method: "update_cost",
|
||||
freeze: true,
|
||||
args: {
|
||||
update_parent: true,
|
||||
from_child_bom:false
|
||||
save: save_doc,
|
||||
from_child_bom: false
|
||||
},
|
||||
callback: function(r) {
|
||||
refresh_field("items");
|
||||
|
@ -330,7 +330,7 @@ class BOM(WebsiteGenerator):
|
||||
frappe.get_doc("BOM", bom).update_cost(from_child_bom=True)
|
||||
|
||||
if not from_child_bom:
|
||||
frappe.msgprint(_("Cost Updated"))
|
||||
frappe.msgprint(_("Cost Updated"), alert=True)
|
||||
|
||||
def update_parent_cost(self):
|
||||
if self.total_cost:
|
||||
@ -774,7 +774,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite
|
||||
item.image,
|
||||
bom.project,
|
||||
bom_item.rate,
|
||||
bom_item.amount,
|
||||
sum(bom_item.{qty_field}/ifnull(bom.quantity, 1)) * bom_item.rate * %(qty)s as amount,
|
||||
item.stock_uom,
|
||||
item.item_group,
|
||||
item.allow_alternative_item,
|
||||
@ -1069,13 +1069,6 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
if barcodes:
|
||||
or_cond_filters["name"] = ("in", barcodes)
|
||||
|
||||
for cond in get_match_cond(doctype, as_condition=False):
|
||||
for key, value in cond.items():
|
||||
if key == doctype:
|
||||
key = "name"
|
||||
|
||||
query_filters[key] = ("in", value)
|
||||
|
||||
if filters and filters.get("item_code"):
|
||||
has_variants = frappe.get_cached_value("Item", filters.get("item_code"), "has_variants")
|
||||
if not has_variants:
|
||||
@ -1084,7 +1077,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
if filters and filters.get("is_stock_item"):
|
||||
query_filters["is_stock_item"] = 1
|
||||
|
||||
return frappe.get_all("Item",
|
||||
return frappe.get_list("Item",
|
||||
fields = fields, filters=query_filters,
|
||||
or_filters = or_cond_filters, order_by=order_by,
|
||||
limit_start=start, limit_page_length=page_len, as_list=1)
|
||||
|
@ -192,11 +192,11 @@ class JobCard(Document):
|
||||
"completed_qty": args.get("completed_qty") or 0.0
|
||||
})
|
||||
elif args.get("start_time"):
|
||||
new_args = {
|
||||
new_args = frappe._dict({
|
||||
"from_time": get_datetime(args.get("start_time")),
|
||||
"operation": args.get("sub_operation"),
|
||||
"completed_qty": 0.0
|
||||
}
|
||||
})
|
||||
|
||||
if employees:
|
||||
for name in employees:
|
||||
|
@ -747,9 +747,8 @@ 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)
|
||||
|
||||
def get_warehouse_list(warehouses, warehouse_list=None):
|
||||
if not warehouse_list:
|
||||
warehouse_list = []
|
||||
def get_warehouse_list(warehouses):
|
||||
warehouse_list = []
|
||||
|
||||
if isinstance(warehouses, str):
|
||||
warehouses = json.loads(warehouses)
|
||||
@ -761,23 +760,19 @@ def get_warehouse_list(warehouses, warehouse_list=None):
|
||||
else:
|
||||
warehouse_list.append(row.get("warehouse"))
|
||||
|
||||
return warehouse_list
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None):
|
||||
if isinstance(doc, str):
|
||||
doc = frappe._dict(json.loads(doc))
|
||||
|
||||
warehouse_list = []
|
||||
if warehouses:
|
||||
get_warehouse_list(warehouses, warehouse_list)
|
||||
|
||||
if warehouse_list:
|
||||
warehouses = list(set(warehouse_list))
|
||||
warehouses = list(set(get_warehouse_list(warehouses)))
|
||||
|
||||
if doc.get("for_warehouse") and not get_parent_warehouse_data and doc.get("for_warehouse") in warehouses:
|
||||
warehouses.remove(doc.get("for_warehouse"))
|
||||
|
||||
warehouse_list = None
|
||||
|
||||
doc['mr_items'] = []
|
||||
|
||||
po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items')
|
||||
|
@ -10,7 +10,7 @@ from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.manufacturing.doctype.production_plan.production_plan import get_sales_orders
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests
|
||||
from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests, get_warehouse_list
|
||||
|
||||
class TestProductionPlan(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@ -251,6 +251,27 @@ class TestProductionPlan(unittest.TestCase):
|
||||
pln.cancel()
|
||||
frappe.delete_doc("Production Plan", pln.name)
|
||||
|
||||
def test_get_warehouse_list_group(self):
|
||||
"""Check if required warehouses are returned"""
|
||||
warehouse_json = '[{\"warehouse\":\"_Test Warehouse Group - _TC\"}]'
|
||||
|
||||
warehouses = set(get_warehouse_list(warehouse_json))
|
||||
expected_warehouses = {"_Test Warehouse Group-C1 - _TC", "_Test Warehouse Group-C2 - _TC"}
|
||||
|
||||
missing_warehouse = expected_warehouses - warehouses
|
||||
|
||||
self.assertTrue(len(missing_warehouse) == 0,
|
||||
msg=f"Following warehouses were expected {', '.join(missing_warehouse)}")
|
||||
|
||||
def test_get_warehouse_list_single(self):
|
||||
warehouse_json = '[{\"warehouse\":\"_Test Scrap Warehouse - _TC\"}]'
|
||||
|
||||
warehouses = set(get_warehouse_list(warehouse_json))
|
||||
expected_warehouses = {"_Test Scrap Warehouse - _TC", }
|
||||
|
||||
self.assertEqual(warehouses, expected_warehouses)
|
||||
|
||||
|
||||
def create_production_plan(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
|
@ -487,21 +487,20 @@ class WorkOrder(Document):
|
||||
return
|
||||
|
||||
operations = []
|
||||
if not self.use_multi_level_bom:
|
||||
bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
|
||||
operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty))
|
||||
else:
|
||||
|
||||
if self.use_multi_level_bom:
|
||||
bom_tree = frappe.get_doc("BOM", self.bom_no).get_tree_representation()
|
||||
bom_traversal = list(reversed(bom_tree.level_order_traversal()))
|
||||
bom_traversal.append(bom_tree) # add operation on top level item last
|
||||
bom_traversal = reversed(bom_tree.level_order_traversal())
|
||||
|
||||
for d in bom_traversal:
|
||||
if d.is_bom:
|
||||
operations.extend(_get_operations(d.name, qty=d.exploded_qty))
|
||||
for node in bom_traversal:
|
||||
if node.is_bom:
|
||||
operations.extend(_get_operations(node.name, qty=node.exploded_qty))
|
||||
|
||||
for correct_index, operation in enumerate(operations, start=1):
|
||||
operation.idx = correct_index
|
||||
bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
|
||||
operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty))
|
||||
|
||||
for correct_index, operation in enumerate(operations, start=1):
|
||||
operation.idx = correct_index
|
||||
|
||||
self.set('operations', operations)
|
||||
self.calculate_time()
|
||||
@ -656,7 +655,7 @@ class WorkOrder(Document):
|
||||
for item in sorted(item_dict.values(), key=lambda d: d['idx'] or 9999):
|
||||
self.append('required_items', {
|
||||
'rate': item.rate,
|
||||
'amount': item.amount,
|
||||
'amount': item.rate * item.qty,
|
||||
'operation': item.operation or operation,
|
||||
'item_code': item.item_code,
|
||||
'item_name': item.item_name,
|
||||
|
@ -296,3 +296,4 @@ erpnext.patches.v13_0.update_job_card_details
|
||||
erpnext.patches.v13_0.update_level_in_bom #1234sswef
|
||||
erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry
|
||||
erpnext.patches.v13_0.update_subscription_status_in_memberships
|
||||
erpnext.patches.v13_0.update_amt_in_work_order_required_items
|
||||
|
@ -10,6 +10,7 @@ def execute():
|
||||
if not frappe.db.has_column('Work Order', 'has_batch_no'):
|
||||
return
|
||||
|
||||
frappe.reload_doc('manufacturing', 'doctype', 'manufacturing_settings')
|
||||
if cint(frappe.db.get_single_value('Manufacturing Settings', 'make_serial_no_batch_from_work_order')):
|
||||
return
|
||||
|
||||
@ -107,4 +108,4 @@ def repost_future_sle_and_gle(doc):
|
||||
"company": doc.company
|
||||
})
|
||||
|
||||
create_repost_item_valuation_entry(args)
|
||||
create_repost_item_valuation_entry(args)
|
||||
|
@ -37,7 +37,7 @@ def execute():
|
||||
|
||||
if frappe.db.exists('DocType', 'Opportunity'):
|
||||
opportunities = frappe.db.get_all('Opportunity', fields=['name', 'mins_to_first_response'], order_by='creation desc')
|
||||
frappe.reload_doc('crm', 'doctype', 'opportunity')
|
||||
frappe.reload_doctype('Opportunity', force=True)
|
||||
rename_field('Opportunity', 'mins_to_first_response', 'first_response_time')
|
||||
|
||||
# change fieldtype to duration
|
||||
|
@ -0,0 +1,10 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
""" Correct amount in child table of required items table."""
|
||||
|
||||
frappe.reload_doc("manufacturing", "doctype", "work_order")
|
||||
frappe.reload_doc("manufacturing", "doctype", "work_order_item")
|
||||
|
||||
frappe.db.sql("""UPDATE `tabWork Order Item` SET amount = rate * required_qty""")
|
||||
|
@ -7,6 +7,7 @@ import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe import _, bold
|
||||
from frappe.utils import getdate, date_diff, comma_and, formatdate
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
|
||||
class AdditionalSalary(Document):
|
||||
def on_submit(self):
|
||||
@ -19,6 +20,7 @@ class AdditionalSalary(Document):
|
||||
self.update_employee_referral(cancel=True)
|
||||
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_dates()
|
||||
self.validate_salary_structure()
|
||||
self.validate_recurring_additional_salary_overlap()
|
||||
|
@ -9,10 +9,11 @@ from frappe.utils import date_diff, getdate, rounded, add_days, cstr, cint, flt
|
||||
from frappe.model.document import Document
|
||||
from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period_days, get_period_factor
|
||||
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
|
||||
from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount
|
||||
from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount, validate_active_employee
|
||||
|
||||
class EmployeeBenefitApplication(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_duplicate_on_payroll_period()
|
||||
if not self.max_benefits:
|
||||
self.max_benefits = get_max_benefits_remaining(self.employee, self.date, self.payroll_period)
|
||||
|
@ -8,12 +8,13 @@ from frappe import _
|
||||
from frappe.utils import flt
|
||||
from frappe.model.document import Document
|
||||
from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_max_benefits
|
||||
from erpnext.hr.utils import get_previous_claimed_amount
|
||||
from erpnext.hr.utils import get_previous_claimed_amount, validate_active_employee
|
||||
from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period
|
||||
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
|
||||
|
||||
class EmployeeBenefitClaim(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
max_benefits = get_max_benefits(self.employee, self.claim_date)
|
||||
if not max_benefits or max_benefits <= 0:
|
||||
frappe.throw(_("Employee {0} has no maximum benefit amount").format(self.employee))
|
||||
|
@ -6,9 +6,11 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
|
||||
class EmployeeIncentive(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_salary_structure()
|
||||
|
||||
def validate_salary_structure(self):
|
||||
|
@ -8,11 +8,12 @@ from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \
|
||||
from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \
|
||||
calculate_annual_eligible_hra_exemption, validate_duplicate_exemption_for_payroll_period
|
||||
|
||||
class EmployeeTaxExemptionDeclaration(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
validate_tax_declaration(self.declarations)
|
||||
validate_duplicate_exemption_for_payroll_period(self.doctype, self.name, self.payroll_period, self.employee)
|
||||
self.set_total_declared_amount()
|
||||
|
@ -7,11 +7,12 @@ import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \
|
||||
from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \
|
||||
calculate_hra_exemption_for_period, validate_duplicate_exemption_for_payroll_period
|
||||
|
||||
class EmployeeTaxExemptionProofSubmission(Document):
|
||||
def validate(self):
|
||||
validate_active_employee(self.employee)
|
||||
validate_tax_declaration(self.tax_exemption_proofs)
|
||||
self.set_total_actual_amount()
|
||||
self.set_total_exemption_amount()
|
||||
|
@ -7,11 +7,10 @@ import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from frappe.utils import getdate
|
||||
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
class RetentionBonus(Document):
|
||||
def validate(self):
|
||||
if frappe.get_value('Employee', self.employee, 'status') != 'Active':
|
||||
frappe.throw(_('Cannot create Retention Bonus for Left or Inactive Employees'))
|
||||
validate_active_employee(self.employee)
|
||||
if getdate(self.bonus_payment_date) < getdate():
|
||||
frappe.throw(_('Bonus Payment Date cannot be a past date'))
|
||||
|
||||
|
@ -4,11 +4,18 @@
|
||||
frappe.ui.form.on('Salary Component', {
|
||||
setup: function(frm) {
|
||||
frm.set_query("account", "accounts", function(doc, cdt, cdn) {
|
||||
var d = locals[cdt][cdn];
|
||||
let d = frappe.get_doc(cdt, cdn);
|
||||
|
||||
let root_type = "Liability";
|
||||
if (frm.doc.type == "Deduction") {
|
||||
root_type = "Expense";
|
||||
}
|
||||
|
||||
return {
|
||||
filters: {
|
||||
"is_group": 0,
|
||||
"company": d.company
|
||||
"company": d.company,
|
||||
"root_type": root_type
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -19,6 +19,7 @@ from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_appli
|
||||
from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits
|
||||
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts, create_repayment_entry
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
from six import iteritems
|
||||
|
||||
class SalarySlip(TransactionBase):
|
||||
@ -39,6 +40,7 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
def validate(self):
|
||||
self.status = self.get_status()
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_dates()
|
||||
self.check_existing()
|
||||
if not self.salary_slip_based_on_timesheet:
|
||||
|
@ -14,6 +14,7 @@ from erpnext.hr.doctype.daily_work_summary.daily_work_summary import get_users_e
|
||||
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
|
||||
from frappe.model.document import Document
|
||||
from erpnext.education.doctype.student_attendance.student_attendance import get_holiday_list
|
||||
from erpnext.controllers.employee_boarding_controller import update_employee_boarding_status
|
||||
|
||||
class Project(Document):
|
||||
def get_feed(self):
|
||||
@ -37,6 +38,7 @@ class Project(Document):
|
||||
self.send_welcome_email()
|
||||
self.update_costing()
|
||||
self.update_percent_complete()
|
||||
update_employee_boarding_status(self)
|
||||
|
||||
def copy_from_template(self):
|
||||
'''
|
||||
@ -132,6 +134,7 @@ class Project(Document):
|
||||
def update_project(self):
|
||||
'''Called externally by Task'''
|
||||
self.update_percent_complete()
|
||||
update_employee_boarding_status(self)
|
||||
self.update_costing()
|
||||
self.db_update()
|
||||
|
||||
|
@ -15,12 +15,15 @@ from erpnext.manufacturing.doctype.workstation.workstation import (check_if_with
|
||||
WorkstationHolidayError)
|
||||
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
|
||||
class OverlapError(frappe.ValidationError): pass
|
||||
class OverWorkLoggedError(frappe.ValidationError): pass
|
||||
|
||||
class Timesheet(Document):
|
||||
def validate(self):
|
||||
if self.employee:
|
||||
validate_active_employee(self.employee)
|
||||
self.set_employee_name()
|
||||
self.set_status()
|
||||
self.validate_dates()
|
||||
|
@ -16,7 +16,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
|
||||
doctype: "Bank Transaction",
|
||||
filters: { name: this.bank_transaction_name },
|
||||
fieldname: [
|
||||
"date",
|
||||
"date as reference_date",
|
||||
"deposit",
|
||||
"withdrawal",
|
||||
"currency",
|
||||
|
16
erpnext/public/js/contact.js
Normal file
16
erpnext/public/js/contact.js
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
|
||||
frappe.ui.form.on("Contact", {
|
||||
refresh(frm) {
|
||||
frm.set_query('link_doctype', "links", function() {
|
||||
return {
|
||||
query: "frappe.contacts.address_and_contact.filter_dynamic_link_doctypes",
|
||||
filters: {
|
||||
fieldtype: ["in", ["HTML", "Text Editor"]],
|
||||
fieldname: ["in", ["contact_html", "company_description"]],
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.refresh_field("links");
|
||||
}
|
||||
});
|
@ -65,7 +65,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
this.frm.refresh_fields();
|
||||
}
|
||||
|
||||
calculate_discount_amount(){
|
||||
calculate_discount_amount() {
|
||||
if (frappe.meta.get_docfield(this.frm.doc.doctype, "discount_amount")) {
|
||||
this.calculate_item_values();
|
||||
this.calculate_net_total();
|
||||
@ -75,18 +75,15 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
}
|
||||
|
||||
_calculate_taxes_and_totals() {
|
||||
frappe.run_serially([
|
||||
() => this.validate_conversion_rate(),
|
||||
() => this.calculate_item_values(),
|
||||
() => this.update_item_tax_map(),
|
||||
() => this.initialize_taxes(),
|
||||
() => this.determine_exclusive_rate(),
|
||||
() => this.calculate_net_total(),
|
||||
() => this.calculate_taxes(),
|
||||
() => this.manipulate_grand_total_for_inclusive_tax(),
|
||||
() => this.calculate_totals(),
|
||||
() => this._cleanup()
|
||||
]);
|
||||
this.validate_conversion_rate();
|
||||
this.calculate_item_values();
|
||||
this.initialize_taxes();
|
||||
this.determine_exclusive_rate();
|
||||
this.calculate_net_total();
|
||||
this.calculate_taxes();
|
||||
this.manipulate_grand_total_for_inclusive_tax();
|
||||
this.calculate_totals();
|
||||
this._cleanup();
|
||||
}
|
||||
|
||||
validate_conversion_rate() {
|
||||
@ -270,46 +267,6 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]);
|
||||
}
|
||||
|
||||
update_item_tax_map() {
|
||||
let me = this;
|
||||
let item_codes = [];
|
||||
let item_rates = {};
|
||||
let item_tax_templates = {};
|
||||
|
||||
$.each(this.frm.doc.items || [], function(i, item) {
|
||||
if (item.item_code) {
|
||||
// Use combination of name and item code in case same item is added multiple times
|
||||
item_codes.push([item.item_code, item.name]);
|
||||
item_rates[item.name] = item.net_rate;
|
||||
item_tax_templates[item.name] = item.item_tax_template;
|
||||
}
|
||||
});
|
||||
|
||||
if (item_codes.length) {
|
||||
return this.frm.call({
|
||||
method: "erpnext.stock.get_item_details.get_item_tax_info",
|
||||
args: {
|
||||
company: me.frm.doc.company,
|
||||
tax_category: cstr(me.frm.doc.tax_category),
|
||||
item_codes: item_codes,
|
||||
item_rates: item_rates,
|
||||
item_tax_templates: item_tax_templates
|
||||
},
|
||||
callback: function(r) {
|
||||
if (!r.exc) {
|
||||
$.each(me.frm.doc.items || [], function(i, item) {
|
||||
if (item.name && r.message.hasOwnProperty(item.name) && r.message[item.name].item_tax_template) {
|
||||
item.item_tax_template = r.message[item.name].item_tax_template;
|
||||
item.item_tax_rate = r.message[item.name].item_tax_rate;
|
||||
me.add_taxes_from_item_tax_template(item.item_tax_rate);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
add_taxes_from_item_tax_template(item_tax_map) {
|
||||
let me = this;
|
||||
|
||||
@ -634,8 +591,6 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
tax.item_wise_tax_detail = JSON.stringify(tax.item_wise_tax_detail);
|
||||
});
|
||||
}
|
||||
|
||||
this.frm.refresh_fields();
|
||||
}
|
||||
|
||||
set_discount_amount() {
|
||||
|
@ -846,9 +846,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
|
||||
frappe.run_serially([
|
||||
() => me.frm.script_manager.trigger("currency"),
|
||||
() => me.update_item_tax_map(),
|
||||
() => me.apply_default_taxes(),
|
||||
() => me.apply_pricing_rule(),
|
||||
() => me.calculate_taxes_and_totals()
|
||||
() => me.apply_pricing_rule()
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -1807,6 +1807,46 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
]);
|
||||
}
|
||||
|
||||
update_item_tax_map() {
|
||||
let me = this;
|
||||
let item_codes = [];
|
||||
let item_rates = {};
|
||||
let item_tax_templates = {};
|
||||
|
||||
$.each(this.frm.doc.items || [], function(i, item) {
|
||||
if (item.item_code) {
|
||||
// Use combination of name and item code in case same item is added multiple times
|
||||
item_codes.push([item.item_code, item.name]);
|
||||
item_rates[item.name] = item.net_rate;
|
||||
item_tax_templates[item.name] = item.item_tax_template;
|
||||
}
|
||||
});
|
||||
|
||||
if (item_codes.length) {
|
||||
return this.frm.call({
|
||||
method: "erpnext.stock.get_item_details.get_item_tax_info",
|
||||
args: {
|
||||
company: me.frm.doc.company,
|
||||
tax_category: cstr(me.frm.doc.tax_category),
|
||||
item_codes: item_codes,
|
||||
item_rates: item_rates,
|
||||
item_tax_templates: item_tax_templates
|
||||
},
|
||||
callback: function(r) {
|
||||
if (!r.exc) {
|
||||
$.each(me.frm.doc.items || [], function(i, item) {
|
||||
if (item.name && r.message.hasOwnProperty(item.name) && r.message[item.name].item_tax_template) {
|
||||
item.item_tax_template = r.message[item.name].item_tax_template;
|
||||
item.item_tax_rate = r.message[item.name].item_tax_rate;
|
||||
me.add_taxes_from_item_tax_template(item.item_tax_rate);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
item_tax_template(doc, cdt, cdn) {
|
||||
var me = this;
|
||||
if(me.frm.updating_party_details) return;
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
frappe.ui.form.on('E Invoice Settings', {
|
||||
refresh(frm) {
|
||||
const docs_link = 'https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing';
|
||||
const docs_link = 'https://docs.erpnext.com/docs/v13/user/manual/en/regional/india/setup-e-invoicing';
|
||||
frm.dashboard.set_headline(
|
||||
__("Read {0} for more information on E Invoicing features.", [`<a href='${docs_link}'>documentation</a>`])
|
||||
);
|
||||
|
@ -214,9 +214,8 @@ class GSTR3BReport(Document):
|
||||
|
||||
for d in item_details:
|
||||
if d.item_code not in self.invoice_items.get(d.parent, {}):
|
||||
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
|
||||
sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in item_details
|
||||
if i.item_code == d.item_code and i.parent == d.parent))
|
||||
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0)
|
||||
self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
|
||||
|
||||
if d.is_nil_exempt and d.item_code not in self.is_nil_exempt:
|
||||
self.is_nil_exempt.append(d.item_code)
|
||||
@ -322,6 +321,9 @@ class GSTR3BReport(Document):
|
||||
inter_state_supply_details[(gst_category, place_of_supply)]['txval'] += taxable_value
|
||||
inter_state_supply_details[(gst_category, place_of_supply)]['iamt'] += (taxable_value * rate /100)
|
||||
|
||||
if self.invoice_cess.get(inv):
|
||||
self.report_dict['sup_details']['osup_det']['csamt'] += flt(self.invoice_cess.get(inv), 2)
|
||||
|
||||
self.set_inter_state_supply(inter_state_supply_details)
|
||||
|
||||
def set_supplies_liable_to_reverse_charge(self):
|
||||
|
@ -217,9 +217,8 @@ class Gstr1Report(object):
|
||||
|
||||
for d in items:
|
||||
if d.item_code not in self.invoice_items.get(d.parent, {}):
|
||||
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
|
||||
sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items
|
||||
if i.item_code == d.item_code and i.parent == d.parent))
|
||||
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0)
|
||||
self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
|
||||
|
||||
item_tax_rate = {}
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
Sales campaign / promotion, like special discount, exhibition, newsletter etc.
|
@ -1 +0,0 @@
|
||||
from __future__ import unicode_literals
|
@ -1,15 +0,0 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.ui.form.on("Campaign", "refresh", function(frm) {
|
||||
erpnext.toggle_naming_series();
|
||||
if(frm.doc.__islocal) {
|
||||
frm.toggle_display("naming_series", frappe.boot.sysdefaults.campaign_naming_by=="Naming Series");
|
||||
}
|
||||
else{
|
||||
cur_frm.add_custom_button(__("View Leads"), function() {
|
||||
frappe.route_options = {"source": "Campaign","campaign_name": frm.doc.name}
|
||||
frappe.set_route("List", "Lead");
|
||||
}, "fa fa-list", true);
|
||||
}
|
||||
})
|
@ -1,17 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
return {
|
||||
'fieldname': 'campaign_name',
|
||||
'transactions': [
|
||||
{
|
||||
'label': _('Email Campaigns'),
|
||||
'items': ['Email Campaign']
|
||||
},
|
||||
{
|
||||
'label': _('Social Media Campaigns'),
|
||||
'items': ['Social Media Post']
|
||||
}
|
||||
]
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
import frappe
|
||||
test_records = frappe.get_test_records('Campaign')
|
@ -1,10 +0,0 @@
|
||||
[
|
||||
{
|
||||
"campaign_name": "_Test Campaign",
|
||||
"doctype": "Campaign"
|
||||
},
|
||||
{
|
||||
"campaign_name": "_Test Campaign 1",
|
||||
"doctype": "Campaign"
|
||||
}
|
||||
]
|
@ -1,3 +1,4 @@
|
||||
.funnel-wrapper {
|
||||
margin: 15px;
|
||||
width: 100%;
|
||||
}
|
@ -162,8 +162,15 @@ class MaterialRequest(BuyingController):
|
||||
from `tabStock Entry Detail` where material_request = %s
|
||||
and material_request_item = %s and docstatus = 1""",
|
||||
(self.name, d.name))[0][0])
|
||||
mr_qty_allowance = frappe.db.get_single_value('Stock Settings', 'mr_qty_allowance')
|
||||
|
||||
if d.ordered_qty and d.ordered_qty > d.stock_qty:
|
||||
if mr_qty_allowance:
|
||||
allowed_qty = d.qty + (d.qty * (mr_qty_allowance/100))
|
||||
if d.ordered_qty and d.ordered_qty > allowed_qty:
|
||||
frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1} \
|
||||
cannot be greater than allowed requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, allowed_qty, d.item_code))
|
||||
|
||||
elif d.ordered_qty and d.ordered_qty > d.stock_qty:
|
||||
frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1} \
|
||||
cannot be greater than requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, d.qty, d.item_code))
|
||||
|
||||
|
@ -329,6 +329,58 @@ class TestMaterialRequest(unittest.TestCase):
|
||||
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
|
||||
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
|
||||
|
||||
def test_over_transfer_qty_allowance(self):
|
||||
mr = frappe.new_doc('Material Request')
|
||||
mr.company = "_Test Company"
|
||||
mr.scheduled_date = today()
|
||||
mr.append('items',{
|
||||
"item_code": "_Test FG Item",
|
||||
"item_name": "_Test FG Item",
|
||||
"qty": 10,
|
||||
"schedule_date": today(),
|
||||
"uom": "_Test UOM 1",
|
||||
"warehouse": "_Test Warehouse - _TC"
|
||||
})
|
||||
|
||||
mr.material_request_type = "Material Transfer"
|
||||
mr.insert()
|
||||
mr.submit()
|
||||
|
||||
frappe.db.set_value('Stock Settings', None, 'mr_qty_allowance', 20)
|
||||
|
||||
# map a stock entry
|
||||
|
||||
se_doc = make_stock_entry(mr.name)
|
||||
se_doc.update({
|
||||
"posting_date": today(),
|
||||
"posting_time": "00:00",
|
||||
})
|
||||
se_doc.get("items")[0].update({
|
||||
"qty": 13,
|
||||
"transfer_qty": 12.0,
|
||||
"s_warehouse": "_Test Warehouse - _TC",
|
||||
"t_warehouse": "_Test Warehouse 1 - _TC",
|
||||
"basic_rate": 1.0
|
||||
})
|
||||
|
||||
# make available the qty in _Test Warehouse 1 before transfer
|
||||
sr = frappe.new_doc("Stock Reconciliation")
|
||||
sr.company = "_Test Company"
|
||||
sr.purpose = "Opening Stock"
|
||||
sr.append('items', {
|
||||
"item_code": "_Test FG Item",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"qty": 20,
|
||||
"valuation_rate": 0.01
|
||||
})
|
||||
sr.insert()
|
||||
sr.submit()
|
||||
se = frappe.copy_doc(se_doc)
|
||||
se.insert()
|
||||
self.assertRaises(frappe.ValidationError)
|
||||
se.items[0].qty = 12
|
||||
se.submit()
|
||||
|
||||
def test_completed_qty_for_over_transfer(self):
|
||||
existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
|
||||
existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
|
||||
|
@ -436,7 +436,7 @@ class PurchaseReceipt(BuyingController):
|
||||
"cost_center": cost_center,
|
||||
"debit": debit,
|
||||
"credit": credit,
|
||||
"against_account": against_account,
|
||||
"against": against_account,
|
||||
"remarks": remarks,
|
||||
}
|
||||
|
||||
|
@ -56,25 +56,40 @@ frappe.ui.form.on("Stock Reconciliation", {
|
||||
},
|
||||
|
||||
get_items: function(frm) {
|
||||
let fields = [{
|
||||
label: 'Warehouse', fieldname: 'warehouse', fieldtype: 'Link', options: 'Warehouse', reqd: 1,
|
||||
"get_query": function() {
|
||||
return {
|
||||
"filters": {
|
||||
"company": frm.doc.company,
|
||||
}
|
||||
};
|
||||
let fields = [
|
||||
{
|
||||
label: 'Warehouse',
|
||||
fieldname: 'warehouse',
|
||||
fieldtype: 'Link',
|
||||
options: 'Warehouse',
|
||||
reqd: 1,
|
||||
"get_query": function() {
|
||||
return {
|
||||
"filters": {
|
||||
"company": frm.doc.company,
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Item Code",
|
||||
fieldname: "item_code",
|
||||
fieldtype: "Link",
|
||||
options: "Item",
|
||||
"get_query": function() {
|
||||
return {
|
||||
"filters": {
|
||||
"disabled": 0,
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
label: __("Ignore Empty Stock"),
|
||||
fieldname: "ignore_empty_stock",
|
||||
fieldtype: "Check"
|
||||
}
|
||||
}, {
|
||||
label: "Item Code", fieldname: "item_code", fieldtype: "Link", options: "Item",
|
||||
"get_query": function() {
|
||||
return {
|
||||
"filters": {
|
||||
"disabled": 0,
|
||||
}
|
||||
};
|
||||
}
|
||||
}];
|
||||
];
|
||||
|
||||
frappe.prompt(fields, function(data) {
|
||||
frappe.call({
|
||||
@ -84,22 +99,21 @@ frappe.ui.form.on("Stock Reconciliation", {
|
||||
posting_date: frm.doc.posting_date,
|
||||
posting_time: frm.doc.posting_time,
|
||||
company: frm.doc.company,
|
||||
item_code: data.item_code
|
||||
item_code: data.item_code,
|
||||
ignore_empty_stock: data.ignore_empty_stock
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.exc || !r.message || !r.message.length) return;
|
||||
|
||||
frm.clear_table("items");
|
||||
for (var i=0; i<r.message.length; i++) {
|
||||
var d = frm.add_child("items");
|
||||
$.extend(d, r.message[i]);
|
||||
|
||||
if (!d.qty) {
|
||||
d.qty = 0;
|
||||
}
|
||||
r.message.forEach((row) => {
|
||||
let item = frm.add_child("items");
|
||||
$.extend(item, row);
|
||||
|
||||
if (!d.valuation_rate) {
|
||||
d.valuation_rate = 0;
|
||||
}
|
||||
}
|
||||
item.qty = item.qty || 0;
|
||||
item.valuation_rate = item.valuation_rate || 0;
|
||||
});
|
||||
frm.refresh_field("items");
|
||||
}
|
||||
});
|
||||
|
@ -484,7 +484,8 @@ class StockReconciliation(StockController):
|
||||
self._cancel()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_items(warehouse, posting_date, posting_time, company, item_code=None):
|
||||
def get_items(warehouse, posting_date, posting_time, company, item_code=None, ignore_empty_stock=False):
|
||||
ignore_empty_stock = cint(ignore_empty_stock)
|
||||
items = [frappe._dict({
|
||||
'item_code': item_code,
|
||||
'warehouse': warehouse
|
||||
@ -498,18 +499,24 @@ def get_items(warehouse, posting_date, posting_time, company, item_code=None):
|
||||
|
||||
for d in items:
|
||||
if d.item_code in itemwise_batch_data:
|
||||
stock_bal = get_stock_balance(d.item_code, d.warehouse,
|
||||
posting_date, posting_time, with_valuation_rate=True)
|
||||
valuation_rate = get_stock_balance(d.item_code, d.warehouse,
|
||||
posting_date, posting_time, with_valuation_rate=True)[1]
|
||||
|
||||
for row in itemwise_batch_data.get(d.item_code):
|
||||
args = get_item_data(row, row.qty, stock_bal[1])
|
||||
if ignore_empty_stock and not row.qty:
|
||||
continue
|
||||
|
||||
args = get_item_data(row, row.qty, valuation_rate)
|
||||
res.append(args)
|
||||
else:
|
||||
stock_bal = get_stock_balance(d.item_code, d.warehouse, posting_date, posting_time,
|
||||
with_valuation_rate=True , with_serial_no=cint(d.has_serial_no))
|
||||
qty, valuation_rate, serial_no = stock_bal[0], stock_bal[1], stock_bal[2] if cint(d.has_serial_no) else ''
|
||||
|
||||
args = get_item_data(d, stock_bal[0], stock_bal[1],
|
||||
stock_bal[2] if cint(d.has_serial_no) else '')
|
||||
if ignore_empty_stock and not stock_bal[0]:
|
||||
continue
|
||||
|
||||
args = get_item_data(d, qty, valuation_rate, serial_no)
|
||||
|
||||
res.append(args)
|
||||
|
||||
@ -517,24 +524,44 @@ def get_items(warehouse, posting_date, posting_time, company, item_code=None):
|
||||
|
||||
def get_items_for_stock_reco(warehouse, company):
|
||||
lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
|
||||
items = frappe.db.sql("""
|
||||
select i.name as item_code, i.item_name, bin.warehouse as warehouse, i.has_serial_no, i.has_batch_no
|
||||
from tabBin bin, tabItem i
|
||||
where i.name=bin.item_code and IFNULL(i.disabled, 0) = 0 and i.is_stock_item = 1
|
||||
and i.has_variants = 0 and exists(
|
||||
select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=bin.warehouse
|
||||
)
|
||||
""", (lft, rgt), as_dict=1)
|
||||
items = frappe.db.sql(f"""
|
||||
select
|
||||
i.name as item_code, i.item_name, bin.warehouse as warehouse, i.has_serial_no, i.has_batch_no
|
||||
from
|
||||
tabBin bin, tabItem i
|
||||
where
|
||||
i.name = bin.item_code
|
||||
and IFNULL(i.disabled, 0) = 0
|
||||
and i.is_stock_item = 1
|
||||
and i.has_variants = 0
|
||||
and exists(
|
||||
select name from `tabWarehouse` where lft >= {lft} and rgt <= {rgt} and name = bin.warehouse
|
||||
)
|
||||
""", as_dict=1)
|
||||
|
||||
items += frappe.db.sql("""
|
||||
select i.name as item_code, i.item_name, id.default_warehouse as warehouse, i.has_serial_no, i.has_batch_no
|
||||
from tabItem i, `tabItem Default` id
|
||||
where i.name = id.parent
|
||||
and exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse)
|
||||
and i.is_stock_item = 1 and i.has_variants = 0 and IFNULL(i.disabled, 0) = 0 and id.company=%s
|
||||
select
|
||||
i.name as item_code, i.item_name, id.default_warehouse as warehouse, i.has_serial_no, i.has_batch_no
|
||||
from
|
||||
tabItem i, `tabItem Default` id
|
||||
where
|
||||
i.name = id.parent
|
||||
and exists(
|
||||
select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse
|
||||
)
|
||||
and i.is_stock_item = 1
|
||||
and i.has_variants = 0
|
||||
and IFNULL(i.disabled, 0) = 0
|
||||
and id.company = %s
|
||||
group by i.name
|
||||
""", (lft, rgt, company), as_dict=1)
|
||||
|
||||
# remove duplicates
|
||||
# check if item-warehouse key extracted from each entry exists in set iw_keys
|
||||
# and update iw_keys
|
||||
iw_keys = set()
|
||||
items = [item for item in items if [(item.item_code, item.warehouse) not in iw_keys, iw_keys.add((item.item_code, item.warehouse))][0]]
|
||||
|
||||
return items
|
||||
|
||||
def get_item_data(row, qty, valuation_rate, serial_no=None):
|
||||
|
@ -18,6 +18,7 @@
|
||||
"section_break_9",
|
||||
"over_delivery_receipt_allowance",
|
||||
"role_allowed_to_over_deliver_receive",
|
||||
"mr_qty_allowance",
|
||||
"column_break_12",
|
||||
"auto_insert_price_list_rate_if_missing",
|
||||
"allow_negative_stock",
|
||||
@ -283,6 +284,12 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Action If Quality Inspection Is Rejected",
|
||||
"options": "Stop\nWarn"
|
||||
},
|
||||
{
|
||||
"description": "The percentage you are allowed to transfer more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed transfer 110 units.",
|
||||
"fieldname": "mr_qty_allowance",
|
||||
"fieldtype": "Float",
|
||||
"label": "Over Transfer Allowance"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@ -290,7 +297,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-10 16:17:42.159829",
|
||||
"modified": "2021-06-28 17:02:26.683002",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Settings",
|
||||
@ -310,4 +317,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ def get_data(report_filters):
|
||||
data = []
|
||||
|
||||
filters = {
|
||||
"is_cancelled": 0,
|
||||
"company": report_filters.company,
|
||||
"posting_date": ("<=", report_filters.as_on_date)
|
||||
}
|
||||
@ -34,7 +35,7 @@ def get_data(report_filters):
|
||||
key = (d.voucher_type, d.voucher_no)
|
||||
gl_data = voucher_wise_gl_data.get(key) or {}
|
||||
d.account_value = gl_data.get("account_value", 0)
|
||||
d.difference_value = (d.stock_value - d.account_value)
|
||||
d.difference_value = abs(d.stock_value - d.account_value)
|
||||
if abs(d.difference_value) > 0.1:
|
||||
data.append(d)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user