Compare commits

..

No commits in common. "5b2e3621008440430be91d2e3fb935e7280dca2c" and "e67805c01fd9d07216e9744566a44550de26f05e" have entirely different histories.

5 changed files with 88 additions and 112 deletions

View file

@ -25,7 +25,7 @@ def create_job_from_sales_order(sales_order_name):
try: try:
sales_order = frappe.get_doc("Sales Order", sales_order_name) sales_order = frappe.get_doc("Sales Order", sales_order_name)
project_template = frappe.get_doc("Project Template", "SNW Install") project_template = frappe.get_doc("Project Template", "SNW Install")
new_project = frappe.get_doc({ new_job = frappe.get_doc({
"doctype": "Project", "doctype": "Project",
"custom_address": sales_order.custom_job_address, "custom_address": sales_order.custom_job_address,
# "custom_installation_address": sales_order.custom_installation_address, # "custom_installation_address": sales_order.custom_installation_address,
@ -35,22 +35,13 @@ def create_job_from_sales_order(sales_order_name):
"sales_order": sales_order, "sales_order": sales_order,
"custom_company": sales_order.company "custom_company": sales_order.company
}) })
new_project.insert() new_job.insert()
for sales_order_item in sales_order.items:
new_task = frappe.get_doc({
"doctype": "Task",
"project": new_project.name,
"company": sales_order.company,
"custom_property": sales_order.custom_job_address,
"subject": sales_order_item.description,
})
new_task.insert()
# Iterate through new tasks (if any) and set customer, address # Iterate through new tasks (if any) and set customer, address
# job_tasks = frappe.get_all("Task", filters={"Project": new_job.name}) # job_tasks = frappe.get_all("Task", filters={"Project": new_job.name})
# for task in job_tasks: # for task in job_tasks:
# task.custom_property = new_job.job_address # task.custom_property = new_job.job_address
# task.save() # task.save()
return build_success_response(new_project.as_dict()) return build_success_response(new_job.as_dict())
except Exception as e: except Exception as e:
return build_error_response(str(e), 500) return build_error_response(str(e), 500)

View file

@ -43,32 +43,33 @@ def get_task_status_options():
@frappe.whitelist() @frappe.whitelist()
def get_tasks_due(subject_filter, current_company): def get_tasks_due(subject_filter):
"""Return the number of items due today of the type of subject_filter""" """Return the number of items due today of the type of subject_filter"""
try: try:
filters = {
'subject': ['like', f'%{subject_filter}%'],
'status': ['not in', ["Template", "Completed", "Cancelled"]]
}
count = frappe.db.count("Task", filters=filters)
return build_success_response(count)
except frappe.ValidationError as ve:
return build_error_response(str(ve), 400)
except Exception as e:
return build_error_response(str(e), 500)
due_filters = {
@frappe.whitelist()
def get_tasks_completed(subject_filter):
"""Return the number of items due today marked complete of the type of subject_filter"""
try:
filters = {
'subject': ['like', f'%{subject_filter}%'], 'subject': ['like', f'%{subject_filter}%'],
'status': ['not in', ["Template", "Completed", "Cancelled"]], 'status': ['not in', ["Template", "Cancelled"]]
'company': current_company,
# Add due date filter here
} }
completed_filters = { print("Completed Task filter", filters)
'subject': ['like', f'%{subject_filter}%'], count = frappe.db.count("Task", filters=filters)
'status': ['not in', ["Template", "Cancelled"]], print("Matching Records:", count)
'company': current_company, return build_success_response(count)
# Add due date filter here
}
overdue_filters = {
'subject': ['like', f'%{subject_filter}%'],
'status': ['not in', ["Template", "Completed", "Cancelled"]],
'company': current_company,
# Add overdue date filtering here
}
due_count = frappe.db.count("Task", filters=due_filters)
completed_count = frappe.db.count("Task", filters=completed_filters)
overdue_count = frappe.db.count("Task", filters=overdue_filters)
return build_success_response([due_count, completed_count, overdue_count])
except frappe.ValidationError as ve: except frappe.ValidationError as ve:
return build_error_response(str(ve), 400) return build_error_response(str(ve), 400)
except Exception as e: except Exception as e:

View file

@ -14,8 +14,6 @@ const FRAPPE_LOCK_ESTIMATE_METHOD = "custom_ui.api.db.estimates.lock_estimate";
const FRAPPE_ESTIMATE_UPDATE_RESPONSE_METHOD = "custom_ui.api.db.estimates.manual_response"; const FRAPPE_ESTIMATE_UPDATE_RESPONSE_METHOD = "custom_ui.api.db.estimates.manual_response";
const FRAPPE_GET_ESTIMATE_TEMPLATES_METHOD = "custom_ui.api.db.estimates.get_estimate_templates"; const FRAPPE_GET_ESTIMATE_TEMPLATES_METHOD = "custom_ui.api.db.estimates.get_estimate_templates";
const FRAPPE_CREATE_ESTIMATE_TEMPLATE_METHOD = "custom_ui.api.db.estimates.create_estimate_template"; const FRAPPE_CREATE_ESTIMATE_TEMPLATE_METHOD = "custom_ui.api.db.estimates.create_estimate_template";
const FRAPPE_GET_UNAPPROVED_ESTIMATES_COUNT_METHOD = "custom_ui.api.db.estimates.get_unnaproved_estimates_count";
const FRAPPE_GET_ESTIMATES_HALF_DOWN_COUNT_METHOD = "custom_ui.api.db.estimates.get_estimates_half_down_count";
// Job methods // Job methods
const FRAPPE_GET_JOB_METHOD = "custom_ui.api.db.jobs.get_job"; const FRAPPE_GET_JOB_METHOD = "custom_ui.api.db.jobs.get_job";
const FRAPPE_GET_JOBS_METHOD = "custom_ui.api.db.jobs.get_jobs_table_data"; const FRAPPE_GET_JOBS_METHOD = "custom_ui.api.db.jobs.get_jobs_table_data";
@ -31,6 +29,7 @@ const FRAPPE_GET_TASKS_METHOD = "custom_ui.api.db.tasks.get_tasks_table_data";
const FRAPPE_GET_TASKS_STATUS_OPTIONS = "custom_ui.api.db.tasks.get_task_status_options"; const FRAPPE_GET_TASKS_STATUS_OPTIONS = "custom_ui.api.db.tasks.get_task_status_options";
const FRAPPE_SET_TASK_STATUS_METHOD = "custom_ui.api.db.tasks.set_task_status"; const FRAPPE_SET_TASK_STATUS_METHOD = "custom_ui.api.db.tasks.set_task_status";
const FRAPPE_GET_TASKS_DUE_METHOD = "custom_ui.api.db.tasks.get_tasks_due"; const FRAPPE_GET_TASKS_DUE_METHOD = "custom_ui.api.db.tasks.get_tasks_due";
const FRAPPE_GET_TASKS_COMPLETED_METHOD = "custom_ui.api.db.tasks.get_tasks_completed";
// Invoice methods // Invoice methods
const FRAPPE_GET_INVOICES_METHOD = "custom_ui.api.db.invoices.get_invoice_table_data"; const FRAPPE_GET_INVOICES_METHOD = "custom_ui.api.db.invoices.get_invoice_table_data";
const FRAPPE_UPSERT_INVOICE_METHOD = "custom_ui.api.db.invoices.upsert_invoice"; const FRAPPE_UPSERT_INVOICE_METHOD = "custom_ui.api.db.invoices.upsert_invoice";
@ -265,14 +264,6 @@ class Api {
return await this.request(FRAPPE_CREATE_ESTIMATE_TEMPLATE_METHOD, { data }); return await this.request(FRAPPE_CREATE_ESTIMATE_TEMPLATE_METHOD, { data });
} }
static async getUnapprovedEstimatesCount() {
return await this.request(FRAPPE_GET_UNAPPROVED_ESTIMATES_COUNT_METHOD, {});
}
static async getEstimatesHalfDownCount() {
return await this.request(FRAPPE_GET_ESTIMATES_HALF_DOWN_COUNT_METHOD, {});
}
// ============================================================================ // ============================================================================
// JOB / PROJECT METHODS // JOB / PROJECT METHODS
// ============================================================================ // ============================================================================
@ -445,8 +436,8 @@ class Api {
return await this.request(FRAPPE_SET_TASK_STATUS_METHOD, { taskName, newStatus }); return await this.request(FRAPPE_SET_TASK_STATUS_METHOD, { taskName, newStatus });
} }
static async getTasksDue(subjectFilter, currentCompany) { static async getTasksDue(subjectFilter) {
const result = await this.request(FRAPPE_GET_TASKS_DUE_METHOD, {subjectFilter, currentCompany}); const result = await this.request(FRAPPE_GET_TASKS_DUE_METHOD, {subjectFilter});
return result; return result;
} }

View file

@ -21,7 +21,7 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, watch, nextTick, computed, onUnmounted, toRaw} from "vue"; import { ref, onMounted, watch, nextTick, computed, onUnmounted} from "vue";
import { Chart, registerables } from "chart.js"; import { Chart, registerables } from "chart.js";
// Register Chart.js components // Register Chart.js components
@ -29,7 +29,8 @@ Chart.register(...registerables);
const props = defineProps({ const props = defineProps({
title: String, title: String,
categories: Object, todoNumber: Number,
completedNumber: Number,
loading: { loading: {
type: Boolean, type: Boolean,
default: false, default: false,
@ -37,7 +38,7 @@ const props = defineProps({
}); });
//Constants //Constants
//const categories = ["To-do", "Completed"]; const categories = ["To-do", "Completed"];
//Reactive data //Reactive data
const centerData = ref(null); const centerData = ref(null);
@ -55,19 +56,21 @@ const getHoveredCategoryIndex = () => {
} }
const getCategoryValue = (categoryIndex) => { const getCategoryValue = (categoryIndex) => {
return props.categories.data[categoryIndex]; if (categoryIndex === 0) {
return props.todoNumber
} else {
return props.completedNumber
}
} }
const getChartData = () => { const getChartData = () => {
const categoryData = props.categories.data;
const categoryColors = props.categories.colors;
const chartData = { const chartData = {
name: props.title, name: props.title,
datasets: [ datasets: [
{ {
label: "", label: "",
data: categoryData, data: [props.todoNumber, props.completedNumber],
backgroundColor: categoryColors backgroundColor: ["#b22222", "#4caf50"]
}, },
] ]
}; };
@ -76,11 +79,8 @@ const getChartData = () => {
const updateCenterData = () => { const updateCenterData = () => {
let total = 0; const total = props.todoNumber + props.completedNumber;
for (let i=0; i<props.categories.data.length; i++) { const todos = props.todoNumber;
total += props.categories.data[i];
}
const todos = props.categories.data[0];
if (todos === 0 && total > 0) { if (todos === 0 && total > 0) {
centerData.value = { centerData.value = {
@ -107,14 +107,14 @@ const updateCenterData = () => {
const percentage = total > 0 ? ((value / total) * 100).toFixed(1) + "%" : "0%"; const percentage = total > 0 ? ((value / total) * 100).toFixed(1) + "%" : "0%";
centerData.value = { centerData.value = {
label: props.categories.labels[hoveredCategoryIndex], label: categories[hoveredCategoryIndex],
value: value, value: value,
percentage: percentage, percentage: percentage,
}; };
} else { } else {
centerData.value = { centerData.value = {
label: "To-do", label: "To-do",
value: todos, value: props.todoNumber,
percentage: null, percentage: null,
}; };
} }
@ -180,6 +180,8 @@ const getChartOptions = () => {
const createChart = () => { const createChart = () => {
if (!chartCanvas.value || props.loading) return; if (!chartCanvas.value || props.loading) return;
console.log(`DEBUG: Creating chart for ${props.title}`);
console.log(props);
const ctx = chartCanvas.value.getContext("2d"); const ctx = chartCanvas.value.getContext("2d");
if (chartInstance.value) { if (chartInstance.value) {
@ -212,9 +214,9 @@ onMounted(() => {
createChart(); createChart();
}); });
watch(() => props.categories, (newValue) => { watch(() => props.completedNumber, (newValue) => {
updateChart(); updateChart();
}, {deep: true}); });
</script> </script>

View file

@ -15,7 +15,8 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Locates" title="Locates"
:categories="chartData.locates" :todoNumber="chartData.locates.todo"
:completedNumber="chartData.locates.done"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" @click="navigateTo('/tasks?subject=Locate')"> <button class="sidebar-button" @click="navigateTo('/tasks?subject=Locate')">
@ -36,7 +37,8 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Permits" title="Permits"
:categories="chartData.permits" :todoNumber="chartData.permits.todo"
:completedNumber="chartData.permits.done"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" @click="navigateTo('/tasks?subject=Permit')"> <button class="sidebar-button" @click="navigateTo('/tasks?subject=Permit')">
@ -57,7 +59,8 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Permit Finalization" title="Permit Finalization"
:categories="chartData.permitFinalizations" :todoNumber="permitFinalizationsTodoNumber"
:completedNumber="permitFinalizationsCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" @click="navigateTo('/jobs')"> <button class="sidebar-button" @click="navigateTo('/jobs')">
@ -78,7 +81,8 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Warranty Claims" title="Warranty Claims"
:categories="chartData.warranties" :todoNumber="warrantyTodoNumber"
:completedNumber="warrantyCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" @click="navigateTo('/jobs')"> <button class="sidebar-button" @click="navigateTo('/jobs')">
@ -99,7 +103,8 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Incomplete Bids" title="Incomplete Bids"
:categories="chartData.bids" :todoNumber="bidsTodoNumber"
:completedNumber="bidsCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -121,7 +126,8 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Unapproved Estimates" title="Unapproved Estimates"
:categories="chartData.estimates" :todoNumber="estimatesTodoNumber"
:completedNumber="estimatesCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -143,7 +149,8 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Half Down Payments" title="Half Down Payments"
:categories="chartData.halfDown" :todoNumber="halfDownTodoNumber"
:completedNumber="halfDownCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -165,7 +172,8 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="15 Day Follow Ups" title="15 Day Follow Ups"
:categories="chartData.fifteenDayFollowups" :todoNumber="fifteenDayTodoNumber"
:completedNumber="fifteenDayCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -187,7 +195,8 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Late Balances" title="Late Balances"
:categories="chartData.lateBalances" :todoNumber="balancesTodoNumber"
:completedNumber="balancesCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -209,7 +218,8 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Backflow Tests" title="Backflow Tests"
:categories="chartData.backflows" :todoNumber="backflowsTodoNumber"
:completedNumber="backflowsCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -231,7 +241,8 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Curbing" title="Curbing"
:categories="chartData.curbing" :todoNumber="chartData.curbing.todo"
:completedNumber="chartData.curbing.done"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -253,7 +264,8 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Hydroseeding" title="Hydroseeding"
:categories="chartData.hydroseed" :todoNumber="chartData.hydroseed.todo"
:completedNumber="chartData.hydroseed.done"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -275,7 +287,8 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Machines" title="Machines"
:categories="chartData.machines" :todoNumber="machinesTodoNumber"
:completedNumber="machinesCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -297,7 +310,8 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Deliveries" title="Deliveries"
:categories="chartData.deliveries" :todoNumber="deliveriesTodoNumber"
:completedNumber="delivieriesCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -312,7 +326,7 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted, watch } from "vue"; import { ref, computed, onMounted } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
//import Card from "primevue/card"; //import Card from "primevue/card";
import Card from "../common/Card.vue"; import Card from "../common/Card.vue";
@ -323,14 +337,11 @@ WateringSoil, Soil, Truck, SoilAlt } from "@iconoir/vue";
import Api from "../../api.js"; import Api from "../../api.js";
import DataUtils from "../../utils.js"; import DataUtils from "../../utils.js";
import { useNotificationStore } from "../../stores/notifications-primevue"; import { useNotificationStore } from "../../stores/notifications-primevue";
import { useCompanyStore } from "../../stores/company.js";
//import SimpleChart from "../common/SimpleChart.vue"; //import SimpleChart from "../common/SimpleChart.vue";
import TodoChart from "../common/TodoChart.vue"; import TodoChart from "../common/TodoChart.vue";
const router = useRouter(); const router = useRouter();
const defaultColors = ['blue', 'green', 'red'];
// Dummy data from utils // Dummy data from utils
const clientData = ref(DataUtils.dummyClientData); const clientData = ref(DataUtils.dummyClientData);
const jobData = ref(DataUtils.dummyJobData); const jobData = ref(DataUtils.dummyJobData);
@ -344,24 +355,13 @@ const warrantyTodoNumber = ref(0);
const warrantyCompletedNumber = ref(10); const warrantyCompletedNumber = ref(10);
const chartData = ref({ const chartData = ref({
locates: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors}, locates: {todo: 0, done: 0},
permits: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors}, permits: {todo: 0, done: 0},
curbing: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors}, curbing: {todo: 0, done: 0},
hydroseed: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors}, hydroseed: {todo: 0, done: 0},
permitFinalizations: {labels: ["Todo", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
warranties: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
bids: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
estimates: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
halfDown: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
fifteenDayFollowups: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
lateBalances: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
backflows: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
machines: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
deliveries: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
}); });
const notifications = useNotificationStore(); const notifications = useNotificationStore();
const companyStore = useCompanyStore();
// Computed values for dashboard metrics // Computed values for dashboard metrics
const totalRevenue = computed(() => "$47,250"); const totalRevenue = computed(() => "$47,250");
@ -373,26 +373,17 @@ const avgResponseTime = computed(() => 2.3);
const navigateTo = (path) => { const navigateTo = (path) => {
router.push(path); router.push(path);
}; };
const loadChartData = async() => {
chartData.value.locates.data = await Api.getTasksDue("Locate", companyStore.currentCompany);
chartData.value.permits.data = await Api.getTasksDue("Permit", companyStore.currentCompany);
chartData.value.curbing.data = await Api.getTasksDue("Curbing", companyStore.currentCompany);
chartData.value.hydroseed.data = await Api.getTasksDue("Hydroseed", companyStore.currentCompany);
//Uncomment below when we can check if half-down payments have/can been paid
//chartData.value.estimates.data = await Api.getEstimatesHalfDownCount();
};
onMounted(async() => { onMounted(async() => {
notifications.addWarning("Dashboard metrics are based on dummy data for demonstration purposes. UPDATES COMING SOON!"); notifications.addWarning("Dashboard metrics are based on dummy data for demonstration purposes. UPDATES COMING SOON!");
await loadChartData(); chartData.value.locates.todo = await Api.getTasksDue("Locate");
chartData.value.locates.done = await Api.getTasksCompleted("Locate");
chartData.value.permits.todo = await Api.getTasksDue("Permit");
chartData.value.permits.done = await Api.getTasksCompleted("Permit");
chartData.value.curbing.todo = await Api.getTasksDue("Curbing");
chartData.value.curbing.done = await Api.getTasksCompleted("Curbing");
chartData.value.hydroseed.todo = await Api.getTasksDue("Hydroseed");
chartData.value.hydroseed.done = await Api.getTasksCompleted("Hydroseed");
}); });
watch(() => companyStore.currentCompany, async (newCompany, oldCompany) => {
console.log("Wathcing for new Company");
await loadChartData();
});
</script> </script>
<style scoped> <style scoped>