feat: track Semi-finished goods (including subcontracted items) against Job Cards (#38341)

* feat: Track Semi-finished goods (including subcontracted items) against Job Cards

* feat: option to add raw materials manually against operation
This commit is contained in:
rohitwaghchaure
2024-06-02 01:28:58 +05:30
committed by GitHub
parent 24926ab665
commit 9e9296e444
43 changed files with 2937 additions and 685 deletions

View File

@@ -33,10 +33,11 @@ class BOMConfigurator {
frm: this.frm,
add_item: this.add_item,
add_sub_assembly: this.add_sub_assembly,
set_query_for_workstation: this.set_query_for_workstation,
get_sub_assembly_modal_fields: this.get_sub_assembly_modal_fields,
convert_to_sub_assembly: this.convert_to_sub_assembly,
delete_node: this.delete_node,
edit_qty: this.edit_qty,
edit_bom: this.edit_bom,
load_tree: this.load_tree,
set_default_qty: this.set_default_qty,
};
@@ -107,15 +108,15 @@ class BOMConfigurator {
this.frm?.doc.docstatus === 0
? [
{
label: `${frappe.utils.icon("edit", "sm")} ${__("Qty")}`,
label: __(frappe.utils.icon("edit", "sm") + " BOM"),
click: function (node) {
let view = frappe.views.trees["BOM Configurator"];
view.events.edit_qty(node, view);
view.events.edit_bom(node, view);
},
btnClass: "hidden-xs",
},
{
label: `${frappe.utils.icon("add", "sm")} ${__("Raw Material")}`,
label: __(frappe.utils.icon("add", "sm") + " Raw Material"),
click: function (node) {
let view = frappe.views.trees["BOM Configurator"];
view.events.add_item(node, view);
@@ -126,7 +127,7 @@ class BOMConfigurator {
btnClass: "hidden-xs",
},
{
label: `${frappe.utils.icon("add", "sm")} ${__("Sub Assembly")}`,
label: __(frappe.utils.icon("add", "sm") + " Sub Assembly"),
click: function (node) {
let view = frappe.views.trees["BOM Configurator"];
view.events.add_sub_assembly(node, view);
@@ -156,7 +157,7 @@ class BOMConfigurator {
btnClass: "hidden-xs expand-all-btn",
},
{
label: `${frappe.utils.icon("move", "sm")} ${__("Sub Assembly")}`,
label: __(frappe.utils.icon("move", "sm") + " Sub Assembly"),
click: function (node) {
let view = frappe.views.trees["BOM Configurator"];
view.events.convert_to_sub_assembly(node, view);
@@ -167,7 +168,7 @@ class BOMConfigurator {
btnClass: "hidden-xs",
},
{
label: `${frappe.utils.icon("delete", "sm")} ${__("Item")}`,
label: __(frappe.utils.icon("delete", "sm") + " Item"),
click: function (node) {
let view = frappe.views.trees["BOM Configurator"];
view.events.delete_node(node, view);
@@ -232,18 +233,38 @@ class BOMConfigurator {
);
}
set_query_for_workstation(dialog) {
let workstation = dialog.fields.filter((field) => field.fieldname === "workstation");
if (workstation.length) {
workstation[0].get_query = function () {
let workstation_type = dialog.get_value("workstation_type");
if (workstation_type) {
return {
filters: {
workstation_type: dialog.get_value("workstation_type"),
},
};
}
};
}
}
add_sub_assembly(node, view) {
let dialog = new frappe.ui.Dialog({
fields: view.events.get_sub_assembly_modal_fields(),
fields: view.events.get_sub_assembly_modal_fields(view, node.is_root),
title: __("Add Sub Assembly"),
});
view.events.set_query_for_workstation(dialog);
dialog.show();
view.events.set_default_qty(dialog);
dialog.set_primary_action(__("Add"), () => {
let bom_item = dialog.get_values();
if (dialog.operation && !dialog.workstation_type && !dialog.workstation) {
frappe.throw(__("Either Workstation or Workstation Type is mandatory"));
}
if (!node.data?.parent_id) {
node.data.parent_id = this.frm.doc.name;
}
@@ -255,6 +276,9 @@ class BOMConfigurator {
fg_item: node.data.value,
fg_reference_id: node.data.name || this.frm.doc.name,
bom_item: bom_item,
operation: node.data.operation,
workstation_type: node.data.workstation_type,
operation_time: node.data.operation_time,
},
callback: (r) => {
view.events.load_tree(r, node);
@@ -265,8 +289,8 @@ class BOMConfigurator {
});
}
get_sub_assembly_modal_fields(read_only = false) {
return [
get_sub_assembly_modal_fields(view, is_root = false, read_only = false, show_operations_fields = false) {
let fields = [
{
label: __("Sub Assembly Item"),
fieldname: "item_code",
@@ -284,37 +308,150 @@ class BOMConfigurator {
reqd: 1,
read_only: read_only,
},
{ fieldtype: "Section Break" },
{
label: __("Raw Materials"),
fieldname: "items",
fieldtype: "Table",
reqd: 1,
fields: [
{
label: __("Item"),
fieldname: "item_code",
fieldtype: "Link",
options: "Item",
reqd: 1,
in_list_view: 1,
},
{
label: __("Qty"),
fieldname: "qty",
default: 1.0,
fieldtype: "Float",
reqd: 1,
in_list_view: 1,
},
],
},
];
if (this.frm.doc.track_operations && (is_root || show_operations_fields)) {
fields.push(
...[
{ fieldtype: "Section Break" },
{
label: __("Operation"),
fieldname: "operation",
fieldtype: "Link",
options: "Operation",
reqd: 1,
},
{
label: __("Operation Time"),
fieldname: "operation_time",
fieldtype: "Int",
reqd: 1,
},
{
label: __("Is Subcontracted"),
fieldname: "is_subcontracted",
fieldtype: "Check",
},
{ fieldtype: "Column Break" },
{
label: __("Workstation Type"),
fieldname: "workstation_type",
fieldtype: "Link",
options: "Workstation Type",
},
{
label: __("Workstation"),
fieldname: "workstation",
fieldtype: "Link",
options: "Workstation",
},
]
);
if (this.frm.doc.track_semi_finished_goods) {
fields.push(
...[
{ label: __("Default Warehouse"), fieldtype: "Section Break", collapsible: 1 },
{
label: __("Skip Material Transfer"),
fieldname: "skip_material_transfer",
fieldtype: "Check",
},
{
label: __("Backflush Materials From WIP"),
fieldname: "backflush_from_wip_warehouse",
fieldtype: "Check",
depends_on: "eval:doc.skip_material_transfer",
},
{
label: __("Source Warehouse"),
fieldname: "source_warehouse",
fieldtype: "Link",
options: "Warehouse",
depends_on: "eval:!doc.backflush_from_wip_warehouse",
get_query() {
return {
filters: {
company: view.events.frm.doc.company,
},
};
},
},
{ fieldtype: "Column Break" },
{
label: __("Work In Progress Warehouse"),
fieldname: "wip_warehouse",
fieldtype: "Link",
options: "Warehouse",
depends_on:
"eval:!doc.skip_material_transfer || doc.backflush_from_wip_warehouse",
get_query() {
return {
filters: {
company: view.events.frm.doc.company,
},
};
},
},
{
label: __("Finished Good Warehouse"),
fieldname: "fg_warehouse",
fieldtype: "Link",
options: "Warehouse",
get_query() {
return {
filters: {
company: view.events.frm.doc.company,
},
};
},
},
]
);
}
}
fields.push(
...[
{ fieldtype: "Section Break" },
{
label: __("Raw Materials"),
fieldname: "items",
fieldtype: "Table",
reqd: 1,
fields: [
{
label: __("Item"),
fieldname: "item_code",
fieldtype: "Link",
options: "Item",
reqd: 1,
in_list_view: 1,
change() {
let doc = this.doc;
doc.qty = 1.0;
this.grid.set_value("qty", 1.0, doc);
},
},
{
label: __("Qty"),
fieldname: "qty",
default: 1.0,
fieldtype: "Float",
reqd: 1,
in_list_view: 1,
},
],
},
]
);
return fields;
}
convert_to_sub_assembly(node, view) {
let dialog = new frappe.ui.Dialog({
fields: view.events.get_sub_assembly_modal_fields(true),
fields: view.events.get_sub_assembly_modal_fields(view, node.is_root, true, true),
title: __("Add Sub Assembly"),
});
@@ -324,11 +461,13 @@ class BOMConfigurator {
});
dialog.show();
view.events.set_default_qty(dialog);
dialog.set_primary_action(__("Add"), () => {
let bom_item = dialog.get_values();
if (dialog.operation && !dialog.workstation_type && !dialog.workstation) {
frappe.throw(__("Either Workstation or Workstation Type is mandatory"));
}
frappe.call({
method: "erpnext.manufacturing.doctype.bom_creator.bom_creator.add_sub_assembly",
args: {
@@ -337,10 +476,14 @@ class BOMConfigurator {
bom_item: bom_item,
fg_reference_id: node.data.name || this.frm.doc.name,
convert_to_sub_assembly: true,
operation: node.data.operation,
workstation_type: node.data.workstation_type,
operation_time: node.data.operation_time,
workstation: node.data.workstation,
},
callback: (r) => {
node.expandable = true;
view.events.load_tree(r, node);
view.events.load_tree(r, node.parent_node);
},
});
@@ -376,24 +519,146 @@ class BOMConfigurator {
});
}
edit_qty(node, view) {
edit_bom(node, view) {
let me = this;
let qty = node.data.qty || this.frm.doc.qty;
frappe.prompt(
[{ label: __("Qty"), fieldname: "qty", default: qty, fieldtype: "Float", reqd: 1 }],
let fields = [{ label: __("Qty"), fieldname: "qty", default: qty, fieldtype: "Float", reqd: 1 }];
if (node.expandable && this.frm.doc.track_operations) {
let data = node.data.operation ? node.data : this.frm.doc;
fields = [
...fields,
...[
{ fieldtype: "Section Break" },
{
label: __("Operation"),
fieldname: "operation",
fieldtype: "Link",
options: "Operation",
default: data.operation,
},
{
label: __("Operation Time"),
fieldname: "operation_time",
fieldtype: "Float",
default: data.operation_time,
},
{
label: __("Is Subcontracted"),
fieldname: "is_subcontracted",
fieldtype: "Check",
default: data.is_subcontracted,
},
{ fieldtype: "Column Break" },
{
label: __("Workstation Type"),
fieldname: "workstation_type",
fieldtype: "Link",
options: "Workstation Type",
default: data.workstation_type,
},
{
label: __("Workstation"),
fieldname: "workstation",
fieldtype: "Link",
options: "Workstation",
default: data.workstation,
get_query() {
let dialog = me.frm.edit_bom_dialog;
let workstation_type = dialog.get_value("workstation_type");
if (workstation_type) {
return {
filters: {
workstation_type: dialog.get_value("workstation_type"),
},
};
}
},
},
{ fieldtype: "Section Break" },
{
label: __("Skip Material Transfer"),
fieldname: "skip_material_transfer",
fieldtype: "Check",
default: data.skip_material_transfer,
},
{
label: __("Backflush Materials From WIP"),
fieldname: "backflush_from_wip_warehouse",
fieldtype: "Check",
depends_on: "eval:doc.skip_material_transfer",
default: data.backflush_from_wip_warehouse,
},
{
label: __("Source Warehouse"),
fieldname: "source_warehouse",
fieldtype: "Link",
options: "Warehouse",
default: data.source_warehouse,
depends_on: "eval:!doc.backflush_from_wip_warehouse",
get_query() {
return {
filters: {
company: me.frm.doc.company,
},
};
},
},
{ fieldtype: "Column Break" },
{
label: __("Work In Progress Warehouse"),
fieldname: "wip_warehouse",
fieldtype: "Link",
options: "Warehouse",
default: data.wip_warehouse,
depends_on: "eval:!doc.skip_material_transfer || doc.backflush_from_wip_warehouse",
get_query() {
return {
filters: {
company: me.frm.doc.company,
},
};
},
},
{
label: __("Finished Good Warehouse"),
fieldname: "fg_warehouse",
fieldtype: "Link",
options: "Warehouse",
default: data.fg_warehouse,
get_query() {
return {
filters: {
company: me.frm.doc.company,
},
};
},
},
],
];
}
this.frm.edit_bom_dialog = frappe.prompt(
fields,
(data) => {
let doctype = node.data.doctype || this.frm.doc.doctype;
let docname = node.data.name || this.frm.doc.name;
frappe.call({
method: "erpnext.manufacturing.doctype.bom_creator.bom_creator.edit_qty",
method: "erpnext.manufacturing.doctype.bom_creator.bom_creator.edit_bom_creator",
args: {
doctype: doctype,
docname: docname,
qty: data.qty,
parent: node.data.parent_id,
data: data,
parent: node.data.parent_id || this.frm.doc.name,
},
callback: (r) => {
node.data.qty = data.qty;
for (let key in data) {
node.data[key] = data[key];
}
let uom = node.data.uom || this.frm.doc.uom;
$(node.parent.get(0))
.find(`[data-bom-qty-docname='${docname}']`)
@@ -402,7 +667,7 @@ class BOMConfigurator {
},
});
},
__("Edit Qty"),
__("Edit BOM"),
__("Update")
);
}

View File

@@ -1,5 +1,8 @@
{% $.each(workstations, (idx, row) => { %}
<div class="workstation-wrapper">
<div class="workstation-status text-right">
<span class="indicator-pill no-indicator-dot whitespace-nowrap {{row.color}}" style="margin: 3px 4px 0px 0px;"><span style="font-size:13px">{{row.status}}</span></span>
</div>
<div class="workstation-image">
<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
<a class="workstation-image-link" href="{{row.workstation_link}}">
@@ -11,9 +14,10 @@
</a>
</div>
</div>
<div class="workstation-card text-center">
<p style="background-color:{{row.background_color}};color:#fff">{{row.status}}</p>
<div>{{row.workstation_name}}</div>
<div class="workstation-card" style="display: grid;">
<span class="ellipsis" title="{{row.name}}">
{{row.workstation_name}}
</span>
</div>
</div>
{% }); %}

View File

@@ -511,6 +511,10 @@ body[data-route="pos"] {
padding-bottom: 25px;
}
.workstation-image-cls {
height: 9rem;
}
.plant-floor-filter {
padding-top: 10px;
display: flex;
@@ -519,8 +523,8 @@ body[data-route="pos"] {
.plant-floor-container {
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: var(--margin-xl);
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: var(--margin-lg);
}
@media screen and (max-width: 620px) {
@@ -536,7 +540,7 @@ body[data-route="pos"] {
.plant-floor-container .workstation-image-link {
width: 100%;
font-size: 50px;
margin: var(--margin-sm);
margin: var(--margin-xs);
min-height: 9rem;
}