diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 532485f21f..5a46002820 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,6 +12,7 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 + persist-credentials: false - name: Setup Node.js v14 uses: actions/setup-node@v2 with: @@ -21,5 +22,10 @@ jobs: npm install @semantic-release/git @semantic-release/exec --no-save - name: Create Release env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.RELEASE_TOKEN }} + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} + GIT_AUTHOR_NAME: "Frappe PR Bot" + GIT_AUTHOR_EMAIL: "developers@frappe.io" + GIT_COMMITTER_NAME: "Frappe PR Bot" + GIT_COMMITTER_EMAIL: "developers@frappe.io" run: npx semantic-release \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py index 81b44f8fea..ced42bbc6e 100644 --- a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py +++ b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py @@ -153,6 +153,31 @@ class TestEmployeeCheckin(FrappeTestCase): log = make_checkin(employee, timestamp) self.assertIsNone(log.shift) + def test_fetch_shift_for_assignment_with_end_date(self): + employee = make_employee("test_employee_checkin@example.com", company="_Test Company") + + # shift setup for 8-12 + shift1 = setup_shift_type() + # 12:30 - 16:30 + shift2 = setup_shift_type(shift_type="Shift 2", start_time="12:30:00", end_time="16:30:00") + + date = getdate() + make_shift_assignment(shift1.name, employee, date, add_days(date, 15)) + make_shift_assignment(shift2.name, employee, date, add_days(date, 15)) + + timestamp = datetime.combine(date, get_time("08:45:00")) + log = make_checkin(employee, timestamp) + self.assertEqual(log.shift, shift1.name) + + timestamp = datetime.combine(date, get_time("12:45:00")) + log = make_checkin(employee, timestamp) + self.assertEqual(log.shift, shift2.name) + + # log after end date + timestamp = datetime.combine(add_days(date, 16), get_time("12:45:00")) + log = make_checkin(employee, timestamp) + self.assertIsNone(log.shift) + def test_shift_start_and_end_timings(self): employee = make_employee("test_employee_checkin@example.com", company="_Test Company") diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index 0b21c00eac..51298deddb 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -121,7 +121,7 @@ def has_overlapping_timings(shift_1: str, shift_2: str) -> bool: @frappe.whitelist() def get_events(start, end, filters=None): - events = [] + from frappe.desk.calendar import get_event_conditions employee = frappe.db.get_value( "Employee", {"user_id": frappe.session.user}, ["name", "company"], as_dict=True @@ -132,20 +132,22 @@ def get_events(start, end, filters=None): employee = "" company = frappe.db.get_value("Global Defaults", None, "default_company") - from frappe.desk.reportview import get_filters_cond - - conditions = get_filters_cond("Shift Assignment", filters, []) - add_assignments(events, start, end, conditions=conditions) + conditions = get_event_conditions("Shift Assignment", filters) + events = add_assignments(start, end, conditions=conditions) return events -def add_assignments(events, start, end, conditions=None): +def add_assignments(start, end, conditions=None): + events = [] + query = """select name, start_date, end_date, employee_name, employee, docstatus, shift_type from `tabShift Assignment` where - start_date >= %(start_date)s - or end_date <= %(end_date)s - or (%(start_date)s between start_date and end_date and %(end_date)s between start_date and end_date) + ( + start_date >= %(start_date)s + or end_date <= %(end_date)s + or (%(start_date)s between start_date and end_date and %(end_date)s between start_date and end_date) + ) and docstatus = 1""" if conditions: query += conditions @@ -251,7 +253,7 @@ def get_shifts_for_date(employee: str, for_timestamp: datetime) -> List[Dict[str Criterion.any( [ assignment.end_date.isnull(), - (assignment.end_date.isnotnull() & (getdate(for_timestamp.date()) >= assignment.end_date)), + (assignment.end_date.isnotnull() & (getdate(for_timestamp.date()) <= assignment.end_date)), ] ) ) diff --git a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py index 0fe9108168..de82a2432b 100644 --- a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py @@ -8,7 +8,7 @@ from frappe.tests.utils import FrappeTestCase from frappe.utils import add_days, getdate, nowdate from erpnext.hr.doctype.employee.test_employee import make_employee -from erpnext.hr.doctype.shift_assignment.shift_assignment import OverlappingShiftError +from erpnext.hr.doctype.shift_assignment.shift_assignment import OverlappingShiftError, get_events from erpnext.hr.doctype.shift_type.test_shift_type import make_shift_assignment, setup_shift_type test_dependencies = ["Shift Type"] @@ -154,3 +154,18 @@ class TestShiftAssignment(FrappeTestCase): shift_type = setup_shift_type(shift_type="Shift 2", start_time="13:00:00", end_time="15:00:00") date = getdate() make_shift_assignment(shift_type.name, employee, date) + + def test_shift_assignment_calendar(self): + employee1 = make_employee("test_shift_assignment1@example.com", company="_Test Company") + employee2 = make_employee("test_shift_assignment2@example.com", company="_Test Company") + + shift_type = setup_shift_type(shift_type="Shift 1", start_time="08:00:00", end_time="12:00:00") + date = getdate() + shift1 = make_shift_assignment(shift_type.name, employee1, date) + make_shift_assignment(shift_type.name, employee2, date) + + events = get_events( + start=date, end=date, filters=[["Shift Assignment", "employee", "=", employee1, False]] + ) + self.assertEqual(len(events), 1) + self.assertEqual(events[0]["name"], shift1.name) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index b2824e139c..b6646b19f6 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -73,10 +73,22 @@ frappe.ui.form.on('Job Card', { if (frm.doc.docstatus == 0 && !frm.is_new() && (frm.doc.for_quantity > frm.doc.total_completed_qty || !frm.doc.for_quantity) && (frm.doc.items || !frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) { - frm.trigger("prepare_timer_buttons"); + + // if Job Card is link to Work Order, the job card must not be able to start if Work Order not "Started" + // and if stock mvt for WIP is required + if (frm.doc.work_order) { + frappe.db.get_value('Work Order', frm.doc.work_order, ['skip_transfer', 'status'], (result) => { + if (result.skip_transfer === 1 || result.status == 'In Process') { + frm.trigger("prepare_timer_buttons"); + } + }); + } else { + frm.trigger("prepare_timer_buttons"); + } } frm.trigger("setup_quality_inspection"); + if (frm.doc.work_order) { frappe.db.get_value('Work Order', frm.doc.work_order, 'transfer_material_against').then((r) => { diff --git a/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py b/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py index edd0a9706b..45acf49205 100644 --- a/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py +++ b/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py @@ -10,54 +10,58 @@ def execute(): frappe.reload_doc("hr", "doctype", "Leave Encashment") - additional_salaries = frappe.get_all( - "Additional Salary", - fields=["name", "salary_slip", "type", "salary_component"], - filters={"salary_slip": ["!=", ""]}, - group_by="salary_slip", - ) - leave_encashments = frappe.get_all( - "Leave Encashment", - fields=["name", "additional_salary"], - filters={"additional_salary": ["!=", ""]}, - ) - employee_incentives = frappe.get_all( - "Employee Incentive", - fields=["name", "additional_salary"], - filters={"additional_salary": ["!=", ""]}, - ) - - for incentive in employee_incentives: - frappe.db.sql( - """ UPDATE `tabAdditional Salary` - SET ref_doctype = 'Employee Incentive', ref_docname = %s - WHERE name = %s - """, - (incentive["name"], incentive["additional_salary"]), + if frappe.db.has_column("Leave Encashment", "additional_salary"): + leave_encashments = frappe.get_all( + "Leave Encashment", + fields=["name", "additional_salary"], + filters={"additional_salary": ["!=", ""]}, ) - - for leave_encashment in leave_encashments: - frappe.db.sql( - """ UPDATE `tabAdditional Salary` - SET ref_doctype = 'Leave Encashment', ref_docname = %s - WHERE name = %s - """, - (leave_encashment["name"], leave_encashment["additional_salary"]), - ) - - salary_slips = [sal["salary_slip"] for sal in additional_salaries] - - for salary in additional_salaries: - comp_type = "earnings" if salary["type"] == "Earning" else "deductions" - if salary["salary_slip"] and salary_slips.count(salary["salary_slip"]) == 1: + for leave_encashment in leave_encashments: frappe.db.sql( - """ - UPDATE `tabSalary Detail` - SET additional_salary = %s - WHERE parenttype = 'Salary Slip' - and parentfield = %s - and parent = %s - and salary_component = %s + """ UPDATE `tabAdditional Salary` + SET ref_doctype = 'Leave Encashment', ref_docname = %s + WHERE name = %s """, - (salary["name"], comp_type, salary["salary_slip"], salary["salary_component"]), + (leave_encashment["name"], leave_encashment["additional_salary"]), ) + + if frappe.db.has_column("Employee Incentive", "additional_salary"): + employee_incentives = frappe.get_all( + "Employee Incentive", + fields=["name", "additional_salary"], + filters={"additional_salary": ["!=", ""]}, + ) + + for incentive in employee_incentives: + frappe.db.sql( + """ UPDATE `tabAdditional Salary` + SET ref_doctype = 'Employee Incentive', ref_docname = %s + WHERE name = %s + """, + (incentive["name"], incentive["additional_salary"]), + ) + + if frappe.db.has_column("Additional Salary", "salary_slip"): + additional_salaries = frappe.get_all( + "Additional Salary", + fields=["name", "salary_slip", "type", "salary_component"], + filters={"salary_slip": ["!=", ""]}, + group_by="salary_slip", + ) + + salary_slips = [sal["salary_slip"] for sal in additional_salaries] + + for salary in additional_salaries: + comp_type = "earnings" if salary["type"] == "Earning" else "deductions" + if salary["salary_slip"] and salary_slips.count(salary["salary_slip"]) == 1: + frappe.db.sql( + """ + UPDATE `tabSalary Detail` + SET additional_salary = %s + WHERE parenttype = 'Salary Slip' + and parentfield = %s + and parent = %s + and salary_component = %s + """, + (salary["name"], comp_type, salary["salary_slip"], salary["salary_component"]), + ) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index f317569312..a97ad792f3 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -1074,7 +1074,7 @@ class GSPConnector: "Distance": cint(eway_bill_details.distance), "TransMode": eway_bill_details.mode_of_transport, "TransId": eway_bill_details.gstin, - "TransName": eway_bill_details.transporter, + "TransName": eway_bill_details.name, "TrnDocDt": eway_bill_details.document_date, "TrnDocNo": eway_bill_details.document_name, "VehNo": eway_bill_details.vehicle_no, diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py index a41051331f..dcfb10a9d5 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py @@ -81,7 +81,7 @@ def get_data(conditions, filters): ON sii.so_detail = soi.name and sii.docstatus = 1) LEFT JOIN `tabDelivery Note Item` dni on dni.so_detail = soi.name - RIGHT JOIN `tabDelivery Note` dn + LEFT JOIN `tabDelivery Note` dn on dni.parent = dn.name and dn.docstatus = 1 WHERE soi.parent = so.name diff --git a/erpnext/selling/report/sales_order_analysis/test_sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/test_sales_order_analysis.py new file mode 100644 index 0000000000..25cbb73449 --- /dev/null +++ b/erpnext/selling/report/sales_order_analysis/test_sales_order_analysis.py @@ -0,0 +1,166 @@ +import frappe +from frappe.tests.utils import FrappeTestCase +from frappe.utils import add_days + +from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note, make_sales_invoice +from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order +from erpnext.selling.report.sales_order_analysis.sales_order_analysis import execute +from erpnext.stock.doctype.item.test_item import create_item + +test_dependencies = ["Sales Order", "Item", "Sales Invoice", "Delivery Note"] + + +class TestSalesOrderAnalysis(FrappeTestCase): + def create_sales_order(self, transaction_date): + item = create_item(item_code="_Test Excavator", is_stock_item=0) + so = make_sales_order( + transaction_date=transaction_date, + item=item.item_code, + qty=10, + rate=100000, + do_not_save=True, + ) + so.po_no = "" + so.taxes_and_charges = "" + so.taxes = "" + so.items[0].delivery_date = add_days(transaction_date, 15) + so.save() + so.submit() + return item, so + + def create_sales_invoice(self, so): + sinv = make_sales_invoice(so.name) + sinv.posting_date = so.transaction_date + sinv.taxes_and_charges = "" + sinv.taxes = "" + sinv.insert() + sinv.submit() + return sinv + + def create_delivery_note(self, so): + dn = make_delivery_note(so.name) + dn.set_posting_time = True + dn.posting_date = add_days(so.transaction_date, 1) + dn.save() + dn.submit() + return dn + + def test_01_so_to_deliver_and_bill(self): + transaction_date = "2021-06-01" + item, so = self.create_sales_order(transaction_date) + columns, data, message, chart = execute( + { + "company": "_Test Company", + "from_date": "2021-06-01", + "to_date": "2021-06-30", + "status": ["To Deliver and Bill"], + } + ) + expected_value = { + "status": "To Deliver and Bill", + "sales_order": so.name, + "delay_days": frappe.utils.date_diff(frappe.utils.datetime.date.today(), so.delivery_date), + "qty": 10, + "delivered_qty": 0, + "pending_qty": 10, + "qty_to_bill": 10, + "time_taken_to_deliver": 0, + } + self.assertEqual(len(data), 1) + for key, val in expected_value.items(): + with self.subTest(key=key, val=val): + self.assertEqual(data[0][key], val) + + def test_02_so_to_deliver(self): + transaction_date = "2021-06-01" + item, so = self.create_sales_order(transaction_date) + self.create_sales_invoice(so) + columns, data, message, chart = execute( + { + "company": "_Test Company", + "from_date": "2021-06-01", + "to_date": "2021-06-30", + "status": ["To Deliver"], + } + ) + expected_value = { + "status": "To Deliver", + "sales_order": so.name, + "delay_days": frappe.utils.date_diff(frappe.utils.datetime.date.today(), so.delivery_date), + "qty": 10, + "delivered_qty": 0, + "pending_qty": 10, + "qty_to_bill": 0, + "time_taken_to_deliver": 0, + } + self.assertEqual(len(data), 1) + for key, val in expected_value.items(): + with self.subTest(key=key, val=val): + self.assertEqual(data[0][key], val) + + def test_03_so_to_bill(self): + transaction_date = "2021-06-01" + item, so = self.create_sales_order(transaction_date) + self.create_delivery_note(so) + columns, data, message, chart = execute( + { + "company": "_Test Company", + "from_date": "2021-06-01", + "to_date": "2021-06-30", + "status": ["To Bill"], + } + ) + expected_value = { + "status": "To Bill", + "sales_order": so.name, + "delay_days": frappe.utils.date_diff(frappe.utils.datetime.date.today(), so.delivery_date), + "qty": 10, + "delivered_qty": 10, + "pending_qty": 0, + "qty_to_bill": 10, + "time_taken_to_deliver": 86400, + } + self.assertEqual(len(data), 1) + for key, val in expected_value.items(): + with self.subTest(key=key, val=val): + self.assertEqual(data[0][key], val) + + def test_04_so_completed(self): + transaction_date = "2021-06-01" + item, so = self.create_sales_order(transaction_date) + self.create_sales_invoice(so) + self.create_delivery_note(so) + columns, data, message, chart = execute( + { + "company": "_Test Company", + "from_date": "2021-06-01", + "to_date": "2021-06-30", + "status": ["Completed"], + } + ) + expected_value = { + "status": "Completed", + "sales_order": so.name, + "delay_days": frappe.utils.date_diff(frappe.utils.datetime.date.today(), so.delivery_date), + "qty": 10, + "delivered_qty": 10, + "pending_qty": 0, + "qty_to_bill": 0, + "billed_qty": 10, + "time_taken_to_deliver": 86400, + } + self.assertEqual(len(data), 1) + for key, val in expected_value.items(): + with self.subTest(key=key, val=val): + self.assertEqual(data[0][key], val) + + def test_05_all_so_status(self): + columns, data, message, chart = execute( + { + "company": "_Test Company", + "from_date": "2021-06-01", + "to_date": "2021-06-30", + } + ) + # SO's from first 4 test cases should be in output + self.assertEqual(len(data), 4)