mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-08 15:42:52 +00:00
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
This commit is contained in:
@@ -4302,6 +4302,154 @@ class TestWorkOrder(ERPNextTestSuite):
|
|||||||
if row.s_warehouse:
|
if row.s_warehouse:
|
||||||
self.assertIn(row.item_code, [raw_material_1, raw_material_2])
|
self.assertIn(row.item_code, [raw_material_1, raw_material_2])
|
||||||
|
|
||||||
|
def test_non_stock_items_shown_in_work_order(self):
|
||||||
|
"""Non stock, non phantom raw materials should appear in non_stock_items with scaled qty & amount."""
|
||||||
|
fg_item = make_item("_Test WO Non Stock FG", {"is_stock_item": 1}).name
|
||||||
|
stock_rm = make_item(
|
||||||
|
"_Test WO Non Stock - Stock RM", {"is_stock_item": 1, "valuation_rate": 100}
|
||||||
|
).name
|
||||||
|
non_stock_rm = make_item(
|
||||||
|
"_Test WO Non Stock - Non Stock RM", {"is_stock_item": 0, "valuation_rate": 7}
|
||||||
|
).name
|
||||||
|
|
||||||
|
bom = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "BOM",
|
||||||
|
"item": fg_item,
|
||||||
|
"currency": "INR",
|
||||||
|
"quantity": 8,
|
||||||
|
"company": "_Test Company",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
bom.append("items", {"item_code": stock_rm, "qty": 5})
|
||||||
|
bom.append("items", {"item_code": non_stock_rm, "qty": 3})
|
||||||
|
bom.insert()
|
||||||
|
bom.submit()
|
||||||
|
|
||||||
|
wo_order = make_wo_order_test_record(
|
||||||
|
production_item=fg_item, bom_no=bom.name, qty=20, skip_transfer=1, do_not_save=True
|
||||||
|
)
|
||||||
|
|
||||||
|
non_stock_items = wo_order.non_stock_items
|
||||||
|
# only the non stock, non phantom item is shown; the stock item is excluded
|
||||||
|
self.assertEqual(len(non_stock_items), 1)
|
||||||
|
row = non_stock_items[0]
|
||||||
|
self.assertEqual(row.item_code, non_stock_rm)
|
||||||
|
# qty = (bom_item_qty / bom_qty) * wo_qty = (3 / 8) * 20 = 7.5
|
||||||
|
self.assertEqual(flt(row.qty, 6), 7.5)
|
||||||
|
# amount = base_rate * qty = 7 * 7.5 = 52.5
|
||||||
|
self.assertEqual(flt(row.amount, 6), 52.5)
|
||||||
|
|
||||||
|
def test_secondary_items_from_bom_without_manufacture_entry(self):
|
||||||
|
"""Without any manufacture entry, secondary items are derived from the BOM with scaled qty & amount."""
|
||||||
|
fg_item = make_item("_Test WO Sec BOM FG", {"is_stock_item": 1}).name
|
||||||
|
stock_rm = make_item("_Test WO Sec BOM RM", {"is_stock_item": 1, "valuation_rate": 100}).name
|
||||||
|
scrap_item = make_item("_Test WO Sec BOM Scrap", {"is_stock_item": 1, "valuation_rate": 0}).name
|
||||||
|
|
||||||
|
bom = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "BOM",
|
||||||
|
"item": fg_item,
|
||||||
|
"currency": "INR",
|
||||||
|
"quantity": 8,
|
||||||
|
"company": "_Test Company",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
bom.append("items", {"item_code": stock_rm, "qty": 2})
|
||||||
|
bom.append(
|
||||||
|
"secondary_items",
|
||||||
|
{
|
||||||
|
"type": "Scrap",
|
||||||
|
"item_code": scrap_item,
|
||||||
|
"item_name": scrap_item,
|
||||||
|
"qty": 3,
|
||||||
|
"cost_allocation_per": 25,
|
||||||
|
"process_loss_per": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
bom.insert()
|
||||||
|
bom.submit()
|
||||||
|
# cost = raw_material_cost * (cost_allocation_per / 100) = 200 * 0.25 = 50
|
||||||
|
self.assertEqual(flt(bom.secondary_items[0].cost, 6), 50.0)
|
||||||
|
|
||||||
|
wo_order = make_wo_order_test_record(
|
||||||
|
production_item=fg_item, bom_no=bom.name, qty=20, skip_transfer=1
|
||||||
|
)
|
||||||
|
|
||||||
|
secondary_items = wo_order.secondary_items
|
||||||
|
self.assertEqual(len(secondary_items), 1)
|
||||||
|
row = secondary_items[0]
|
||||||
|
self.assertEqual(row.item_code, scrap_item)
|
||||||
|
self.assertEqual(row.type, "Scrap")
|
||||||
|
# data is fetched from the BOM (carries bom_qty)
|
||||||
|
self.assertEqual(flt(row.bom_qty), 8.0)
|
||||||
|
# qty = (bom_secondary_qty / bom_qty) * wo_qty = (3 / 8) * 20 = 7.5
|
||||||
|
self.assertEqual(flt(row.qty, 6), 7.5)
|
||||||
|
# amount = cost * qty = 50 * 7.5 = 375
|
||||||
|
self.assertEqual(flt(row.amount, 6), 375.0)
|
||||||
|
|
||||||
|
def test_secondary_items_reflect_manufacture_entry(self):
|
||||||
|
"""Once a manufacture entry exists, secondary items reflect what was generated, not the BOM."""
|
||||||
|
fg_item = make_item("_Test WO Sec SE FG", {"is_stock_item": 1}).name
|
||||||
|
stock_rm = make_item("_Test WO Sec SE RM", {"is_stock_item": 1, "valuation_rate": 100}).name
|
||||||
|
scrap_item = make_item("_Test WO Sec SE Scrap", {"is_stock_item": 1, "valuation_rate": 0}).name
|
||||||
|
|
||||||
|
bom = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "BOM",
|
||||||
|
"item": fg_item,
|
||||||
|
"currency": "INR",
|
||||||
|
"quantity": 8,
|
||||||
|
"company": "_Test Company",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
bom.append("items", {"item_code": stock_rm, "qty": 2})
|
||||||
|
bom.append(
|
||||||
|
"secondary_items",
|
||||||
|
{
|
||||||
|
"type": "Scrap",
|
||||||
|
"item_code": scrap_item,
|
||||||
|
"item_name": scrap_item,
|
||||||
|
"qty": 3,
|
||||||
|
"cost_allocation_per": 25,
|
||||||
|
"process_loss_per": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
bom.insert()
|
||||||
|
bom.submit()
|
||||||
|
|
||||||
|
wo_order = make_wo_order_test_record(
|
||||||
|
production_item=fg_item,
|
||||||
|
bom_no=bom.name,
|
||||||
|
qty=20,
|
||||||
|
skip_transfer=1,
|
||||||
|
source_warehouse="_Test Warehouse - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
# before any manufacture entry, data comes from the BOM
|
||||||
|
self.assertEqual(flt(wo_order.secondary_items[0].qty, 6), 7.5)
|
||||||
|
|
||||||
|
# make raw material available and manufacture a partial quantity
|
||||||
|
test_stock_entry.make_stock_entry(
|
||||||
|
item_code=stock_rm, target="_Test Warehouse - _TC", qty=100, basic_rate=100
|
||||||
|
)
|
||||||
|
manufacture_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 8))
|
||||||
|
manufacture_entry.submit()
|
||||||
|
|
||||||
|
generated_row = next(row for row in manufacture_entry.items if row.type == "Scrap")
|
||||||
|
|
||||||
|
wo_order.reload()
|
||||||
|
secondary_items = wo_order.secondary_items
|
||||||
|
self.assertEqual(len(secondary_items), 1)
|
||||||
|
row = secondary_items[0]
|
||||||
|
# now sourced from the manufacture entry, not the BOM
|
||||||
|
self.assertIsNone(row.get("bom_qty"))
|
||||||
|
self.assertEqual(row.item_code, scrap_item)
|
||||||
|
self.assertEqual(flt(row.qty, 6), flt(generated_row.qty, 6))
|
||||||
|
self.assertEqual(flt(row.amount, 6), flt(generated_row.amount, 6))
|
||||||
|
# generated qty (3.0 for 8 units) differs from the BOM-scaled qty (7.5 for 20 units)
|
||||||
|
self.assertEqual(flt(row.qty, 6), 3.0)
|
||||||
|
|
||||||
|
|
||||||
def get_reserved_entries(voucher_no, warehouse=None):
|
def get_reserved_entries(voucher_no, warehouse=None):
|
||||||
doctype = frappe.qb.DocType("Stock Reservation Entry")
|
doctype = frappe.qb.DocType("Stock Reservation Entry")
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ frappe.ui.form.on("Work Order", {
|
|||||||
frm.set_indicator_formatter("operation", function (doc) {
|
frm.set_indicator_formatter("operation", function (doc) {
|
||||||
return frm.doc.qty == doc.completed_qty ? "green" : "orange";
|
return frm.doc.qty == doc.completed_qty ? "green" : "orange";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.fields_dict["non_stock_items"].grid.set_column_disp_in_list_view("type", false);
|
||||||
|
frm.fields_dict["secondary_items"].grid.set_column_disp_in_list_view("rate", false);
|
||||||
},
|
},
|
||||||
|
|
||||||
set_company_filters(frm, fieldname) {
|
set_company_filters(frm, fieldname) {
|
||||||
@@ -127,6 +130,15 @@ frappe.ui.form.on("Work Order", {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onload_post_render(frm) {
|
||||||
|
const label = frm.doc.__onload?.secondary_items_generated
|
||||||
|
? __("Secondary Items (as per Manufacture Entries)")
|
||||||
|
: __("Secondary Items (as per BOM)");
|
||||||
|
|
||||||
|
frm.set_df_property("secondary_items", "label", label);
|
||||||
|
frm.fields_dict["secondary_items"].grid.wrapper?.find("> .control-label").text(label);
|
||||||
|
},
|
||||||
|
|
||||||
source_warehouse: function (frm) {
|
source_warehouse: function (frm) {
|
||||||
let transaction_controller = new erpnext.TransactionController();
|
let transaction_controller = new erpnext.TransactionController();
|
||||||
transaction_controller.autofill_warehouse(
|
transaction_controller.autofill_warehouse(
|
||||||
|
|||||||
@@ -71,6 +71,10 @@
|
|||||||
"has_batch_no",
|
"has_batch_no",
|
||||||
"column_break_18",
|
"column_break_18",
|
||||||
"batch_size",
|
"batch_size",
|
||||||
|
"additional_costs_section",
|
||||||
|
"non_stock_items",
|
||||||
|
"secondary_items_section",
|
||||||
|
"secondary_items",
|
||||||
"reference_section",
|
"reference_section",
|
||||||
"project",
|
"project",
|
||||||
"subcontracting_inward_order",
|
"subcontracting_inward_order",
|
||||||
@@ -703,6 +707,30 @@
|
|||||||
"fieldname": "production_item_info_section",
|
"fieldname": "production_item_info_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Production Item Info"
|
"label": "Production Item Info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "additional_costs_section",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "non_stock_items",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"is_virtual": 1,
|
||||||
|
"label": "Additional Costs (as per BOM)",
|
||||||
|
"options": "Work Order Additional Item",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "secondary_items_section",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "secondary_items",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"is_virtual": 1,
|
||||||
|
"label": "Secondary Items (as per BOM)",
|
||||||
|
"options": "Work Order Additional Item",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
@@ -711,7 +739,7 @@
|
|||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2026-05-19 12:20:38.102403",
|
"modified": "2026-06-03 21:35:34.175667",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Work Order",
|
"name": "Work Order",
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ class WorkOrder(Document):
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from frappe.types import DF
|
from frappe.types import DF
|
||||||
|
|
||||||
|
from erpnext.manufacturing.doctype.work_order_additional_item.work_order_additional_item import (
|
||||||
|
WorkOrderAdditionalItem,
|
||||||
|
)
|
||||||
from erpnext.manufacturing.doctype.work_order_item.work_order_item import WorkOrderItem
|
from erpnext.manufacturing.doctype.work_order_item.work_order_item import WorkOrderItem
|
||||||
from erpnext.manufacturing.doctype.work_order_operation.work_order_operation import WorkOrderOperation
|
from erpnext.manufacturing.doctype.work_order_operation.work_order_operation import WorkOrderOperation
|
||||||
|
|
||||||
@@ -106,6 +109,7 @@ class WorkOrder(Document):
|
|||||||
max_producible_qty: DF.Float
|
max_producible_qty: DF.Float
|
||||||
mps: DF.Link | None
|
mps: DF.Link | None
|
||||||
naming_series: DF.Literal["MFG-WO-.YYYY.-"]
|
naming_series: DF.Literal["MFG-WO-.YYYY.-"]
|
||||||
|
non_stock_items: DF.Table[WorkOrderAdditionalItem]
|
||||||
operations: DF.Table[WorkOrderOperation]
|
operations: DF.Table[WorkOrderOperation]
|
||||||
planned_end_date: DF.Datetime | None
|
planned_end_date: DF.Datetime | None
|
||||||
planned_operating_cost: DF.Currency
|
planned_operating_cost: DF.Currency
|
||||||
@@ -124,6 +128,7 @@ class WorkOrder(Document):
|
|||||||
sales_order: DF.Link | None
|
sales_order: DF.Link | None
|
||||||
sales_order_item: DF.Data | None
|
sales_order_item: DF.Data | None
|
||||||
scrap_warehouse: DF.Link | None
|
scrap_warehouse: DF.Link | None
|
||||||
|
secondary_items: DF.Table[WorkOrderAdditionalItem]
|
||||||
skip_transfer: DF.Check
|
skip_transfer: DF.Check
|
||||||
source_warehouse: DF.Link | None
|
source_warehouse: DF.Link | None
|
||||||
status: DF.Literal[
|
status: DF.Literal[
|
||||||
@@ -167,6 +172,69 @@ class WorkOrder(Document):
|
|||||||
if based_on := frappe.get_cached_value("BOM", self.bom_no, "backflush_based_on"):
|
if based_on := frappe.get_cached_value("BOM", self.bom_no, "backflush_based_on"):
|
||||||
self.set_onload("backflush_raw_materials_based_on", based_on)
|
self.set_onload("backflush_raw_materials_based_on", based_on)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def secondary_items(self):
|
||||||
|
parent = frappe.qb.DocType("Stock Entry")
|
||||||
|
child = frappe.qb.DocType("Stock Entry Detail")
|
||||||
|
secondary_items_generated = (
|
||||||
|
frappe.qb.from_(parent)
|
||||||
|
.join(child)
|
||||||
|
.on(parent.name == child.parent)
|
||||||
|
.where(
|
||||||
|
(parent.work_order == self.name)
|
||||||
|
& (parent.docstatus == 1)
|
||||||
|
& ((child.type != "") | (child.is_legacy_scrap_item == 1))
|
||||||
|
)
|
||||||
|
.select(
|
||||||
|
child.item_code,
|
||||||
|
Case().when(child.is_legacy_scrap_item == 1, "Scrap (Legacy)").else_(child.type).as_("type"),
|
||||||
|
child.qty,
|
||||||
|
child.uom,
|
||||||
|
child.amount,
|
||||||
|
)
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
if secondary_items_generated:
|
||||||
|
self.set_onload("secondary_items_generated", True)
|
||||||
|
return secondary_items_generated
|
||||||
|
else:
|
||||||
|
secondary_items = frappe.get_query(
|
||||||
|
"BOM",
|
||||||
|
filters={"name": self.bom_no},
|
||||||
|
fields=[
|
||||||
|
"secondary_items.item_code",
|
||||||
|
"secondary_items.type",
|
||||||
|
"secondary_items.qty",
|
||||||
|
"secondary_items.uom",
|
||||||
|
"secondary_items.cost as amount",
|
||||||
|
"quantity as bom_qty",
|
||||||
|
],
|
||||||
|
).run(as_dict=True)
|
||||||
|
secondary_items = [item for item in secondary_items if item.item_code]
|
||||||
|
for item in secondary_items:
|
||||||
|
item["qty"] = (item.qty / item.bom_qty) * self.qty
|
||||||
|
item["amount"] = flt(item.amount) * item.qty
|
||||||
|
return secondary_items
|
||||||
|
|
||||||
|
@property
|
||||||
|
def non_stock_items(self):
|
||||||
|
non_stock_items = frappe.get_query(
|
||||||
|
"BOM",
|
||||||
|
filters={"name": self.bom_no, "items.is_stock_item": 0, "items.is_phantom_item": 0},
|
||||||
|
fields=[
|
||||||
|
"items.item_code",
|
||||||
|
"items.qty",
|
||||||
|
"items.uom",
|
||||||
|
"items.base_rate as rate",
|
||||||
|
"items.base_amount as amount",
|
||||||
|
"quantity as bom_qty",
|
||||||
|
],
|
||||||
|
).run(as_dict=True)
|
||||||
|
for item in non_stock_items:
|
||||||
|
item["qty"] = (item.qty / item.bom_qty) * self.qty
|
||||||
|
item["amount"] = item.rate * item["qty"]
|
||||||
|
return non_stock_items
|
||||||
|
|
||||||
def show_create_job_card_button(self):
|
def show_create_job_card_button(self):
|
||||||
jc_doctype = frappe.qb.DocType("Job Card")
|
jc_doctype = frappe.qb.DocType("Job Card")
|
||||||
query = (
|
query = (
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"creation": "2026-06-03 15:52:39.829793",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"item_code",
|
||||||
|
"type",
|
||||||
|
"qty",
|
||||||
|
"uom",
|
||||||
|
"column_break_lrsc",
|
||||||
|
"rate",
|
||||||
|
"amount"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "item_code",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Item Code",
|
||||||
|
"options": "Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "type",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_lrsc",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Qty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "uom",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "UOM",
|
||||||
|
"options": "UOM"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Amount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "rate",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Rate"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"is_virtual": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-06-03 21:46:46.738564",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Manufacturing",
|
||||||
|
"name": "Work Order Additional Item",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"rows_threshold_for_grid_search": 20,
|
||||||
|
"sort_field": "creation",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class WorkOrderAdditionalItem(Document):
|
||||||
|
# begin: auto-generated types
|
||||||
|
# This code is auto-generated. Do not modify anything in this block.
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from frappe.types import DF
|
||||||
|
|
||||||
|
amount: DF.Currency
|
||||||
|
item_code: DF.Link | None
|
||||||
|
parent: DF.Data
|
||||||
|
parentfield: DF.Data
|
||||||
|
parenttype: DF.Data
|
||||||
|
qty: DF.Float
|
||||||
|
rate: DF.Currency
|
||||||
|
type: DF.Data | None
|
||||||
|
uom: DF.Link | None
|
||||||
|
# end: auto-generated types
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_list(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_count(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_stats(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def db_insert(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def load_from_db(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def db_update(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
Reference in New Issue
Block a user