mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-04 22:18:27 +00:00
Merge branch 'version-13-hotfix' into crm-contact-duplication-v13
This commit is contained in:
10
.github/workflows/patch.yml
vendored
10
.github/workflows/patch.yml
vendored
@@ -5,9 +5,14 @@ on:
|
||||
paths-ignore:
|
||||
- '**.js'
|
||||
- '**.md'
|
||||
types: [opened, unlabeled, synchronize, reopened]
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
concurrency:
|
||||
group: patch-mariadb-v13-${{ github.event.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-18.04
|
||||
@@ -25,6 +30,11 @@ jobs:
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
|
||||
steps:
|
||||
- name: Check for merge conficts label
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'conflicts') }}
|
||||
run: |
|
||||
echo "Remove merge conflicts and remove conflict label to run CI"
|
||||
exit 1
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
|
||||
|
||||
44
.github/workflows/server-tests.yml
vendored
44
.github/workflows/server-tests.yml
vendored
@@ -5,6 +5,7 @@ on:
|
||||
paths-ignore:
|
||||
- '**.js'
|
||||
- '**.md'
|
||||
types: [opened, unlabeled, synchronize, reopened]
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ develop ]
|
||||
@@ -12,6 +13,10 @@ on:
|
||||
- '**.js'
|
||||
- '**.md'
|
||||
|
||||
concurrency:
|
||||
group: server-mariadb-v13-${{ github.event.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-18.04
|
||||
@@ -35,6 +40,12 @@ jobs:
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
|
||||
steps:
|
||||
- name: Check for merge conficts label
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'conflicts') }}
|
||||
run: |
|
||||
echo "Remove merge conflicts and remove conflict label to run CI"
|
||||
exit 1
|
||||
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
|
||||
@@ -89,39 +100,8 @@ jobs:
|
||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||
|
||||
- name: Run Tests
|
||||
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage
|
||||
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator
|
||||
env:
|
||||
TYPE: server
|
||||
CI_BUILD_ID: ${{ github.run_id }}
|
||||
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
|
||||
|
||||
- name: Upload Coverage Data
|
||||
run: |
|
||||
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
|
||||
cd ${GITHUB_WORKSPACE}
|
||||
pip3 install coverage==5.5
|
||||
pip3 install coveralls==3.0.1
|
||||
coveralls
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COVERALLS_FLAG_NAME: run-${{ matrix.container }}
|
||||
COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }}
|
||||
COVERALLS_PARALLEL: true
|
||||
|
||||
coveralls:
|
||||
name: Coverage Wrap Up
|
||||
needs: test
|
||||
container: python:3-slim
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Coveralls Finished
|
||||
run: |
|
||||
cd ${GITHUB_WORKSPACE}
|
||||
pip3 install coverage==5.5
|
||||
pip3 install coveralls==3.0.1
|
||||
coveralls --finish
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
4
.github/workflows/ui-tests.yml
vendored
4
.github/workflows/ui-tests.yml
vendored
@@ -6,6 +6,10 @@ on:
|
||||
- '**.md'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ui-v13-${{ github.event.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
@@ -17,6 +17,7 @@ from openpyxl.styles import Font
|
||||
from openpyxl.utils import get_column_letter
|
||||
from six import string_types
|
||||
|
||||
INVALID_VALUES = ("", None)
|
||||
|
||||
class BankStatementImport(DataImport):
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -96,6 +97,18 @@ def download_errored_template(data_import_name):
|
||||
data_import = frappe.get_doc("Bank Statement Import", data_import_name)
|
||||
data_import.export_errored_rows()
|
||||
|
||||
def parse_data_from_template(raw_data):
|
||||
data = []
|
||||
|
||||
for i, row in enumerate(raw_data):
|
||||
if all(v in INVALID_VALUES for v in row):
|
||||
# empty row
|
||||
continue
|
||||
|
||||
data.append(row)
|
||||
|
||||
return data
|
||||
|
||||
def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options):
|
||||
"""This method runs in background job"""
|
||||
|
||||
@@ -105,7 +118,8 @@ def start_import(data_import, bank_account, import_file_path, google_sheets_url,
|
||||
file = import_file_path if import_file_path else google_sheets_url
|
||||
|
||||
import_file = ImportFile("Bank Transaction", file = file, import_type="Insert New Records")
|
||||
data = import_file.raw_data
|
||||
|
||||
data = parse_data_from_template(import_file.raw_data)
|
||||
|
||||
if import_file_path:
|
||||
add_bank_account(data, bank_account)
|
||||
|
||||
@@ -201,8 +201,8 @@ def get_course_schedule_events(start, end, filters=None):
|
||||
conditions = get_event_conditions("Course Schedule", filters)
|
||||
|
||||
data = frappe.db.sql("""select name, course, color,
|
||||
timestamp(schedule_date, from_time) as from_datetime,
|
||||
timestamp(schedule_date, to_time) as to_datetime,
|
||||
timestamp(schedule_date, from_time) as from_time,
|
||||
timestamp(schedule_date, to_time) as to_time,
|
||||
room, student_group, 0 as 'allDay'
|
||||
from `tabCourse Schedule`
|
||||
where ( schedule_date between %(start)s and %(end)s )
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
@@ -30,6 +32,14 @@ class CourseSchedule(Document):
|
||||
if self.from_time > self.to_time:
|
||||
frappe.throw(_("From Time cannot be greater than To Time."))
|
||||
|
||||
"""Handles specicfic case to update schedule date in calendar """
|
||||
if isinstance(self.from_time, str):
|
||||
try:
|
||||
datetime_obj = datetime.strptime(self.from_time, '%Y-%m-%d %H:%M:%S')
|
||||
self.schedule_date = datetime_obj
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def validate_overlap(self):
|
||||
"""Validates overlap for Student Group, Instructor, Room"""
|
||||
|
||||
@@ -47,4 +57,4 @@ class CourseSchedule(Document):
|
||||
validate_overlap_for(self, "Assessment Plan", "student_group")
|
||||
|
||||
validate_overlap_for(self, "Assessment Plan", "room")
|
||||
validate_overlap_for(self, "Assessment Plan", "supervisor", self.instructor)
|
||||
validate_overlap_for(self, "Assessment Plan", "supervisor", self.instructor)
|
||||
@@ -1,11 +1,10 @@
|
||||
frappe.views.calendar["Course Schedule"] = {
|
||||
field_map: {
|
||||
// from_datetime and to_datetime don't exist as docfields but are used in onload
|
||||
"start": "from_datetime",
|
||||
"end": "to_datetime",
|
||||
"start": "from_time",
|
||||
"end": "to_time",
|
||||
"id": "name",
|
||||
"title": "course",
|
||||
"allDay": "allDay"
|
||||
"allDay": "allDay",
|
||||
},
|
||||
gantt: false,
|
||||
order_by: "schedule_date",
|
||||
|
||||
@@ -6,6 +6,7 @@ import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.utils import to_timedelta, today
|
||||
from frappe.utils.data import add_to_date
|
||||
|
||||
from erpnext.education.utils import OverlapError
|
||||
|
||||
@@ -39,6 +40,11 @@ class TestCourseSchedule(unittest.TestCase):
|
||||
make_course_schedule_test_record(from_time= cs1.from_time, to_time= cs1.to_time,
|
||||
student_group="Course-TC102-2014-2015 (_Test Academic Term)", instructor="_Test Instructor 2", room=frappe.get_all("Room")[1].name)
|
||||
|
||||
def test_update_schedule_date(self):
|
||||
doc = make_course_schedule_test_record(schedule_date= add_to_date(today(), days=1))
|
||||
doc.schedule_date = add_to_date(doc.schedule_date, days=1)
|
||||
doc.save()
|
||||
|
||||
def make_course_schedule_test_record(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _, throw
|
||||
from frappe.utils import add_days, cint, cstr, date_diff, formatdate, getdate
|
||||
@@ -306,13 +305,18 @@ class MaintenanceSchedule(TransactionBase):
|
||||
return schedule.name
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_serial_nos(s_id):
|
||||
serial_nos = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'serial_no')
|
||||
def get_serial_nos_from_schedule(item_code, schedule=None):
|
||||
serial_nos = []
|
||||
if schedule:
|
||||
serial_nos = frappe.db.get_value('Maintenance Schedule Item', {
|
||||
'parent': schedule,
|
||||
'item_code': item_code
|
||||
}, 'serial_no')
|
||||
|
||||
if serial_nos:
|
||||
serial_nos = get_serial_nos(serial_nos)
|
||||
return serial_nos
|
||||
else:
|
||||
return False
|
||||
|
||||
return serial_nos
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=None):
|
||||
@@ -320,12 +324,9 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No
|
||||
|
||||
def update_status_and_detail(source, target, parent):
|
||||
target.maintenance_type = "Scheduled"
|
||||
target.maintenance_schedule = source.name
|
||||
target.maintenance_schedule_detail = s_id
|
||||
|
||||
def update_sales_and_serial(source, target, parent):
|
||||
sales_person = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'sales_person')
|
||||
target.service_person = sales_person
|
||||
def update_serial(source, target, parent):
|
||||
serial_nos = get_serial_nos(target.serial_no)
|
||||
if len(serial_nos) == 1:
|
||||
target.serial_no = serial_nos[0]
|
||||
@@ -346,7 +347,10 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No
|
||||
"Maintenance Schedule Item": {
|
||||
"doctype": "Maintenance Visit Purpose",
|
||||
"condition": lambda doc: doc.item_name == item_name,
|
||||
"postprocess": update_sales_and_serial
|
||||
"field_map": {
|
||||
"sales_person": "service_person"
|
||||
},
|
||||
"postprocess": update_serial
|
||||
}
|
||||
}, target_doc)
|
||||
|
||||
|
||||
@@ -7,8 +7,11 @@ import frappe
|
||||
from frappe.utils.data import add_days, formatdate, today
|
||||
|
||||
from erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule import (
|
||||
get_serial_nos_from_schedule,
|
||||
make_maintenance_visit,
|
||||
)
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
|
||||
# test_records = frappe.get_test_records('Maintenance Schedule')
|
||||
|
||||
@@ -80,6 +83,42 @@ class TestMaintenanceSchedule(unittest.TestCase):
|
||||
#checks if visit status is back updated in schedule
|
||||
self.assertTrue(ms.schedules[1].completion_status, "Partially Completed")
|
||||
|
||||
def test_serial_no_filters(self):
|
||||
# Without serial no. set in schedule -> returns None
|
||||
item_code = "_Test Serial Item"
|
||||
make_serial_item_with_serial(item_code)
|
||||
ms = make_maintenance_schedule(item_code=item_code)
|
||||
ms.submit()
|
||||
|
||||
s_item = ms.schedules[0]
|
||||
mv = make_maintenance_visit(source_name=ms.name, item_name=item_code, s_id=s_item.name)
|
||||
mvi = mv.purposes[0]
|
||||
serial_nos = get_serial_nos_from_schedule(mvi.item_name, ms.name)
|
||||
self.assertEqual(serial_nos, None)
|
||||
|
||||
# With serial no. set in schedule -> returns serial nos.
|
||||
make_serial_item_with_serial(item_code)
|
||||
ms = make_maintenance_schedule(item_code=item_code, serial_no="TEST001, TEST002")
|
||||
ms.submit()
|
||||
|
||||
s_item = ms.schedules[0]
|
||||
mv = make_maintenance_visit(source_name=ms.name, item_name=item_code, s_id=s_item.name)
|
||||
mvi = mv.purposes[0]
|
||||
serial_nos = get_serial_nos_from_schedule(mvi.item_name, ms.name)
|
||||
self.assertEqual(serial_nos, ["TEST001", "TEST002"])
|
||||
|
||||
frappe.db.rollback()
|
||||
|
||||
def make_serial_item_with_serial(item_code):
|
||||
serial_item_doc = create_item(item_code, is_stock_item=1)
|
||||
if not serial_item_doc.has_serial_no or not serial_item_doc.serial_no_series:
|
||||
serial_item_doc.has_serial_no = 1
|
||||
serial_item_doc.serial_no_series = "TEST.###"
|
||||
serial_item_doc.save(ignore_permissions=True)
|
||||
active_serials = frappe.db.get_all('Serial No', {"status": "Active", "item_code": item_code})
|
||||
if len(active_serials) < 2:
|
||||
make_serialized_item(item_code=item_code)
|
||||
|
||||
def get_events(ms):
|
||||
return frappe.get_all("Event Participants", filters={
|
||||
"reference_doctype": ms.doctype,
|
||||
@@ -87,17 +126,18 @@ def get_events(ms):
|
||||
"parenttype": "Event"
|
||||
})
|
||||
|
||||
def make_maintenance_schedule():
|
||||
def make_maintenance_schedule(**args):
|
||||
ms = frappe.new_doc("Maintenance Schedule")
|
||||
ms.company = "_Test Company"
|
||||
ms.customer = "_Test Customer"
|
||||
ms.transaction_date = today()
|
||||
|
||||
ms.append("items", {
|
||||
"item_code": "_Test Item",
|
||||
"item_code": args.get("item_code") or "_Test Item",
|
||||
"start_date": today(),
|
||||
"periodicity": "Weekly",
|
||||
"no_of_visits": 4,
|
||||
"serial_no": args.get("serial_no"),
|
||||
"sales_person": "Sales Team",
|
||||
})
|
||||
ms.insert(ignore_permissions=True)
|
||||
|
||||
@@ -2,52 +2,54 @@
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.provide("erpnext.maintenance");
|
||||
var serial_nos = [];
|
||||
frappe.ui.form.on('Maintenance Visit', {
|
||||
refresh: function (frm) {
|
||||
//filters for serial_no based on item_code
|
||||
frm.set_query('serial_no', 'purposes', function (frm, cdt, cdn) {
|
||||
let item = locals[cdt][cdn];
|
||||
if (serial_nos) {
|
||||
return {
|
||||
filters: {
|
||||
'item_code': item.item_code,
|
||||
'name': ["in", serial_nos]
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
filters: {
|
||||
'item_code': item.item_code
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
setup: function (frm) {
|
||||
frm.set_query('contact_person', erpnext.queries.contact_query);
|
||||
frm.set_query('customer_address', erpnext.queries.address_query);
|
||||
frm.set_query('customer', erpnext.queries.customer);
|
||||
},
|
||||
onload: function (frm, cdt, cdn) {
|
||||
let item = locals[cdt][cdn];
|
||||
onload: function (frm) {
|
||||
// filters for serial no based on item code
|
||||
if (frm.doc.maintenance_type === "Scheduled") {
|
||||
const schedule_id = item.purposes[0].prevdoc_detail_docname || frm.doc.maintenance_schedule_detail;
|
||||
let item_code = frm.doc.purposes[0].item_code;
|
||||
frappe.call({
|
||||
method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos",
|
||||
method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.get_serial_nos_from_schedule",
|
||||
args: {
|
||||
s_id: schedule_id
|
||||
},
|
||||
callback: function (r) {
|
||||
serial_nos = r.message;
|
||||
schedule: frm.doc.maintenance_schedule,
|
||||
item_code: item_code
|
||||
}
|
||||
}).then((r) => {
|
||||
let serial_nos = r.message;
|
||||
frm.set_query('serial_no', 'purposes', () => {
|
||||
if (serial_nos.length > 0) {
|
||||
return {
|
||||
filters: {
|
||||
'item_code': item_code,
|
||||
'name': ["in", serial_nos]
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
filters: {
|
||||
'item_code': item_code
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
} else {
|
||||
frm.set_query('serial_no', 'purposes', (frm, cdt, cdn) => {
|
||||
let row = locals[cdt][cdn];
|
||||
return {
|
||||
filters: {
|
||||
'item_code': row.item_code
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
if (!frm.doc.status) {
|
||||
frm.set_value({ status: 'Draft' });
|
||||
}
|
||||
if (frm.doc.__islocal) {
|
||||
frm.doc.maintenance_type == 'Unscheduled' && frm.clear_table("purposes");
|
||||
frm.set_value({ mntc_date: frappe.datetime.get_today() });
|
||||
}
|
||||
},
|
||||
@@ -60,7 +62,6 @@ frappe.ui.form.on('Maintenance Visit', {
|
||||
contact_person: function (frm) {
|
||||
erpnext.utils.get_contact_details(frm);
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
// TODO commonify this code
|
||||
|
||||
@@ -179,8 +179,7 @@
|
||||
"label": "Purposes",
|
||||
"oldfieldname": "maintenance_visit_details",
|
||||
"oldfieldtype": "Table",
|
||||
"options": "Maintenance Visit Purpose",
|
||||
"reqd": 1
|
||||
"options": "Maintenance Visit Purpose"
|
||||
},
|
||||
{
|
||||
"fieldname": "more_info",
|
||||
@@ -294,10 +293,11 @@
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-27 16:06:17.352572",
|
||||
"modified": "2021-12-17 03:10:27.608112",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Maintenance",
|
||||
"name": "Maintenance Visit",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
|
||||
@@ -18,6 +18,10 @@ class MaintenanceVisit(TransactionBase):
|
||||
if d.serial_no and not frappe.db.exists("Serial No", d.serial_no):
|
||||
frappe.throw(_("Serial No {0} does not exist").format(d.serial_no))
|
||||
|
||||
def validate_purpose_table(self):
|
||||
if not self.purposes:
|
||||
frappe.throw(_("Add Items in the Purpose Table"), title="Purposes Required")
|
||||
|
||||
def validate_maintenance_date(self):
|
||||
if self.maintenance_type == "Scheduled" and self.maintenance_schedule_detail:
|
||||
item_ref = frappe.db.get_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'item_reference')
|
||||
@@ -29,6 +33,7 @@ class MaintenanceVisit(TransactionBase):
|
||||
def validate(self):
|
||||
self.validate_serial_no()
|
||||
self.validate_maintenance_date()
|
||||
self.validate_purpose_table()
|
||||
|
||||
def update_completion_status(self):
|
||||
if self.maintenance_schedule_detail:
|
||||
|
||||
@@ -949,6 +949,7 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company):
|
||||
warehouses, item.get("quantity"), company, ignore_validation=True)
|
||||
|
||||
required_qty = item.get("quantity")
|
||||
# get available material by transferring to production warehouse
|
||||
for d in locations:
|
||||
if required_qty <=0: return
|
||||
|
||||
@@ -959,12 +960,14 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company):
|
||||
new_dict.update({
|
||||
"quantity": quantity,
|
||||
"material_request_type": "Material Transfer",
|
||||
"uom": new_dict.get("stock_uom"), # internal transfer should be in stock UOM
|
||||
"from_warehouse": d.get("warehouse")
|
||||
})
|
||||
|
||||
required_qty -= quantity
|
||||
new_mr_items.append(new_dict)
|
||||
|
||||
# raise purchase request for remaining qty
|
||||
if required_qty:
|
||||
stock_uom, purchase_uom = frappe.db.get_value(
|
||||
'Item',
|
||||
|
||||
@@ -4,6 +4,39 @@
|
||||
|
||||
frappe.query_reports["BOM Operations Time"] = {
|
||||
"filters": [
|
||||
|
||||
{
|
||||
"fieldname": "item_code",
|
||||
"label": __("Item Code"),
|
||||
"fieldtype": "Link",
|
||||
"width": "100",
|
||||
"options": "Item",
|
||||
"get_query": () =>{
|
||||
return {
|
||||
filters: { "disabled": 0, "is_stock_item": 1 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "bom_id",
|
||||
"label": __("BOM ID"),
|
||||
"fieldtype": "MultiSelectList",
|
||||
"width": "100",
|
||||
"options": "BOM",
|
||||
"get_data": function(txt) {
|
||||
return frappe.db.get_link_options("BOM", txt);
|
||||
},
|
||||
"get_query": () =>{
|
||||
return {
|
||||
filters: { "docstatus": 1, "is_active": 1, "with_operations": 1 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "workstation",
|
||||
"label": __("Workstation"),
|
||||
"fieldtype": "Link",
|
||||
"width": "100",
|
||||
"options": "Workstation"
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"add_total_row": 1,
|
||||
"columns": [],
|
||||
"creation": "2020-03-03 01:41:20.862521",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"letter_head": "",
|
||||
"modified": "2020-03-03 01:41:20.862521",
|
||||
"modified": "2022-01-20 14:21:47.771591",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM Operations Time",
|
||||
|
||||
@@ -12,19 +12,15 @@ def execute(filters=None):
|
||||
return columns, data
|
||||
|
||||
def get_data(filters):
|
||||
data = []
|
||||
bom_wise_data = {}
|
||||
bom_data, report_data = [], []
|
||||
|
||||
bom_data = []
|
||||
for d in frappe.db.sql("""
|
||||
SELECT
|
||||
bom.name, bom.item, bom.item_name, bom.uom,
|
||||
bomps.operation, bomps.workstation, bomps.time_in_mins
|
||||
FROM `tabBOM` bom, `tabBOM Operation` bomps
|
||||
WHERE
|
||||
bom.docstatus = 1 and bom.is_active = 1 and bom.name = bomps.parent
|
||||
""", as_dict=1):
|
||||
bom_operation_data = get_filtered_data(filters)
|
||||
|
||||
for d in bom_operation_data:
|
||||
row = get_args()
|
||||
if d.name not in bom_data:
|
||||
bom_wise_data[d.name] = []
|
||||
bom_data.append(d.name)
|
||||
row.update(d)
|
||||
else:
|
||||
@@ -34,14 +30,49 @@ def get_data(filters):
|
||||
"time_in_mins": d.time_in_mins
|
||||
})
|
||||
|
||||
data.append(row)
|
||||
# maintain BOM wise data for grouping such as:
|
||||
# {"BOM A": [{Row1}, {Row2}], "BOM B": ...}
|
||||
bom_wise_data[d.name].append(row)
|
||||
|
||||
used_as_subassembly_items = get_bom_count(bom_data)
|
||||
|
||||
for d in data:
|
||||
d.used_as_subassembly_items = used_as_subassembly_items.get(d.name, 0)
|
||||
for d in bom_wise_data:
|
||||
for row in bom_wise_data[d]:
|
||||
row.used_as_subassembly_items = used_as_subassembly_items.get(row.name, 0)
|
||||
report_data.append(row)
|
||||
|
||||
return data
|
||||
return report_data
|
||||
|
||||
def get_filtered_data(filters):
|
||||
bom = frappe.qb.DocType("BOM")
|
||||
bom_ops = frappe.qb.DocType("BOM Operation")
|
||||
|
||||
bom_ops_query = (
|
||||
frappe.qb.from_(bom)
|
||||
.join(bom_ops).on(bom.name == bom_ops.parent)
|
||||
.select(
|
||||
bom.name, bom.item, bom.item_name, bom.uom,
|
||||
bom_ops.operation, bom_ops.workstation, bom_ops.time_in_mins
|
||||
).where(
|
||||
(bom.docstatus == 1)
|
||||
& (bom.is_active == 1)
|
||||
)
|
||||
)
|
||||
|
||||
if filters.get("item_code"):
|
||||
bom_ops_query = bom_ops_query.where(bom.item == filters.get("item_code"))
|
||||
|
||||
if filters.get("bom_id"):
|
||||
bom_ops_query = bom_ops_query.where(bom.name.isin(filters.get("bom_id")))
|
||||
|
||||
if filters.get("workstation"):
|
||||
bom_ops_query = bom_ops_query.where(
|
||||
bom_ops.workstation == filters.get("workstation")
|
||||
)
|
||||
|
||||
bom_operation_data = bom_ops_query.run(as_dict=True)
|
||||
|
||||
return bom_operation_data
|
||||
|
||||
def get_bom_count(bom_data):
|
||||
data = frappe.get_all("BOM Item",
|
||||
@@ -68,13 +99,13 @@ def get_columns(filters):
|
||||
"options": "BOM",
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Link",
|
||||
"width": 140
|
||||
"width": 220
|
||||
}, {
|
||||
"label": _("BOM Item Code"),
|
||||
"label": _("Item Code"),
|
||||
"options": "Item",
|
||||
"fieldname": "item",
|
||||
"fieldtype": "Link",
|
||||
"width": 140
|
||||
"width": 150
|
||||
}, {
|
||||
"label": _("Item Name"),
|
||||
"fieldname": "item_name",
|
||||
@@ -85,13 +116,13 @@ def get_columns(filters):
|
||||
"options": "UOM",
|
||||
"fieldname": "uom",
|
||||
"fieldtype": "Link",
|
||||
"width": 140
|
||||
"width": 100
|
||||
}, {
|
||||
"label": _("Operation"),
|
||||
"options": "Operation",
|
||||
"fieldname": "operation",
|
||||
"fieldtype": "Link",
|
||||
"width": 120
|
||||
"width": 140
|
||||
}, {
|
||||
"label": _("Workstation"),
|
||||
"options": "Workstation",
|
||||
@@ -101,11 +132,11 @@ def get_columns(filters):
|
||||
}, {
|
||||
"label": _("Time (In Mins)"),
|
||||
"fieldname": "time_in_mins",
|
||||
"fieldtype": "Int",
|
||||
"width": 140
|
||||
"fieldtype": "Float",
|
||||
"width": 120
|
||||
}, {
|
||||
"label": _("Sub-assembly BOM Count"),
|
||||
"fieldname": "used_as_subassembly_items",
|
||||
"fieldtype": "Int",
|
||||
"width": 180
|
||||
"width": 200
|
||||
}]
|
||||
|
||||
@@ -341,3 +341,4 @@ erpnext.patches.v13_0.disable_ksa_print_format_for_others # 16-12-2021
|
||||
erpnext.patches.v13_0.update_tax_category_for_rcm
|
||||
erpnext.patches.v13_0.convert_to_website_item_in_item_card_group_template
|
||||
erpnext.patches.v13_0.agriculture_deprecation_warning
|
||||
erpnext.patches.v13_0.update_maintenance_schedule_field_in_visit
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("maintenance", "doctype", "maintenance_visit")
|
||||
|
||||
# Updates the Maintenance Schedule link to fetch serial nos
|
||||
from frappe.query_builder.functions import Coalesce
|
||||
mvp = frappe.qb.DocType('Maintenance Visit Purpose')
|
||||
mv = frappe.qb.DocType('Maintenance Visit')
|
||||
|
||||
frappe.qb.update(
|
||||
mv
|
||||
).join(
|
||||
mvp
|
||||
).on(mvp.parent == mv.name).set(
|
||||
mv.maintenance_schedule,
|
||||
Coalesce(mvp.prevdoc_docname, '')
|
||||
).where(
|
||||
(mv.maintenance_type == "Scheduled")
|
||||
& (mvp.prevdoc_docname.notnull())
|
||||
& (mv.docstatus < 2)
|
||||
).run(as_dict=1)
|
||||
Reference in New Issue
Block a user