Passing json document and convert it to doctype

I make custom api and it has a parameter looks like this:

"obj": {
        "doc": {
            "owner": "Administrator",
            "docstatus": 0,
            "idx": 0,
            "period_start_date": "2024-07-17 21:08:15",
            "period_end_date": "2024-07-17 22:46:05.271028",
            "posting_date": "2024-07-17",
            "posting_time": "22:46:05.270879",
            "pos_opening_entry": "POS-OPE-2024-00001",
            "status": "Draft",
            "company": "Ekasa Multi Semesta",
            "pos_profile": "Outlet Utama",
            "user": "Administrator",
            "grand_total": 5000.0,
            "net_total": 5000.0,
            "total_quantity": 1.0,
            "doctype": "POS Closing Entry",
            "taxes": [],
            "payment_reconciliation": [
                {
                    "docstatus": 0,
                    "idx": 1,
                    "mode_of_payment": "Cash",
                    "opening_amount": 0.0,
                    "expected_amount": 5000.0,
                    "closing_amount": 0.0,
                    "difference": 0.0,
                    "parentfield": "payment_reconciliation",
                    "parenttype": "POS Closing Entry",
                    "doctype": "POS Closing Entry Detail",
                    "__islocal": 1
                },
                {
                    "docstatus": 0,
                    "idx": 2,
                    "mode_of_payment": "Bank Transfer",
                    "opening_amount": 0.0,
                    "expected_amount": 0.0,
                    "closing_amount": 0.0,
                    "difference": 0.0,
                    "parentfield": "payment_reconciliation",
                    "parenttype": "POS Closing Entry",
                    "doctype": "POS Closing Entry Detail",
                    "__islocal": 1
                }
            ],
            "pos_transactions": [
                {
                    "docstatus": 0,
                    "idx": 1,
                    "pos_invoice": "ACC-PSINV-2024-00001",
                    "posting_date": "2024-07-17",
                    "customer": "NN",
                    "grand_total": 5000.0,
                    "is_return": 0,
                    "parentfield": "pos_transactions",
                    "parenttype": "POS Closing Entry",
                    "doctype": "POS Invoice Reference",
                    "__islocal": 1
                }
            ],
            "__islocal": 1,
            "__unsaved": 1
        }
    }

which is obj[“doc”] actually a POS Closing Entry Document.

Inside the API, I do this:

closing_entry = frappe.new_doc("POS Closing Entry")
    closing_entry = obj["doc"]
    closing_entry.save()

But it gives me an error : “AttributeError: ‘dict’ object has no attribute ‘save’”

I need to make this custom API and cannot just use regular POST endpoint.

You got an object from frappe.new_doc() function.
Do a print(dir(closing_entry)) with that one, and a print(type(closing_entry)) for good measure, too.

Then you overwrote this object with another object.
Redo the two print statements above, and you will see what happened.

I see.
notice the difference.

So, how I can convert or make my “obj[‘doc’]” to become POS Closing Entry document?

@oscarutomo try this
doc_data = obj[“doc”]
closing_entry = new_doc(“POS Closing Entry”)
for field, value in doc_data.items():
closing_entry.set(field, value)
closing_entry.insert()

1 Like
~/frappe/bench$ bench --site <yoursitename> console

In [8]: closing_entry.as_dict()
Out[8]:
{'name': None,
 'owner': 'Administrator',
 'creation': None,
 'modified': None,
 'modified_by': None,
 'docstatus': 0,
 'idx': 0,
 'period_start_date': None,
 'period_end_date': '2024-07-17',
 'posting_date': '2024-07-17',
 'posting_time': '19:23:03.932327',
 'pos_opening_entry': None,
 'status': 'Draft',
 'company': 'HE',
 'pos_profile': None,
 'user': None,
 'grand_total': 0.0,
 'net_total': 0.0,
 'total_quantity': 0.0,
 'error_message': None,
 'amended_from': None,
 'doctype': 'POS Closing Entry',
 'taxes': [],
 'pos_transactions': [],
 'payment_reconciliation': [],
 '__islocal': 1,
 '__unsaved': 1}

Instead of replacing the new db object as a whole, you should assign your values to its attributes, then it’ll probably stay savable.

You can also assign values directly, like so
(notice that its a field name and not a dict key that’s used here):

In [9]: closing_entry.period_start_date = "2024-07-17 21:08:15"

In [10]: closing_entry.as_dict()
Out[10]:
{'name': None,
 'owner': 'Administrator',
 'creation': None,
 'modified': None,
 'modified_by': None,
 'docstatus': 0,
 'idx': 0,
 'period_start_date': '2024-07-17 21:08:15',
 'period_end_date': '2024-07-17',
 'posting_date': '2024-07-17',
 'posting_time': '19:23:03.932327',
 'pos_opening_entry': None,
 'status': 'Draft',
 'company': 'HE',
 'pos_profile': None,
 'user': None,
 'grand_total': 0.0,
 'net_total': 0.0,
 'total_quantity': 0.0,
 'error_message': None,
 'amended_from': None,
 'doctype': 'POS Closing Entry',
 'taxes': [],
 'pos_transactions': [],
 'payment_reconciliation': [],
 '__islocal': 1,
 '__unsaved': 1}

In [11]: closing_entry.set('period_start_date', '2024-07-17')

In [12]: closing_entry.as_dict()
Out[12]:
{'name': None,
 'owner': 'Administrator',
 'creation': None,
 'modified': None,
 'modified_by': None,
 'docstatus': 0,
 'idx': 0,
 'period_start_date': '2024-07-17',
 'period_end_date': '2024-07-17',
 'posting_date': '2024-07-17',
 'posting_time': '19:23:03.932327',
 'pos_opening_entry': None,
 'status': 'Draft',
 'company': 'HE',
 'pos_profile': None,
 'user': None,
 'grand_total': 0.0,
 'net_total': 0.0,
 'total_quantity': 0.0,
 'error_message': None,
 'amended_from': None,
 'doctype': 'POS Closing Entry',
 'taxes': [],
 'pos_transactions': [],
 'payment_reconciliation': [],
 '__islocal': 1,
 '__unsaved': 1}

In the second assignment I used the method suggested by @Jeel.

Also notice that dir() results in a different set of attributes than the .as_dict() method.

There might be some adaptations needed, because metadata and/or other fields might need to be left untouched – or not --, or the format adapted, etc.

In doubt the “business logic” in the doctype’s .py controller might help figure out what’s needed.

Thanks @Jeel and @Peer

So seems I will need to assign the values to its attributes.

Or maybe this can help:

frappe.get_doc(dict)

Returns a new Document object in memory which does not exist yet in the database.

# create a new document
doc = frappe.get_doc({
    'doctype': 'Task',
    'title': 'New Task'
})
doc.insert()

frappe.get_doc(doctype={document_type}, key1 = value1, key2 = value2, ...)

Returns a new Document object in memory which does not exist yet in the database.
(From here: Document API)

The example has only two fields, but that’s a ToDo which doesn’t have many fields anyway. Try more fields, maybe all of yours, or maybe some of them, or almost all, or maybe all except the metadata, or so, etc.

That’s kind of a block box experimentation, unless you read the code, which might be faster and clear up things more profoundly, after all.

1 Like

thanks Peer. noted.