Merge branch 'develop' into serial-no-space
This commit is contained in:
commit
4c929b6994
@ -5,7 +5,7 @@ import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
from frappe.utils import getdate
|
||||
|
||||
__version__ = '13.7.1'
|
||||
__version__ = '13.8.0'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
|
@ -230,7 +230,7 @@ class Account(NestedSet):
|
||||
if self.check_gle_exists():
|
||||
throw(_("Account with existing transaction can not be converted to group."))
|
||||
elif self.account_type and not self.flags.exclude_account_type_check:
|
||||
throw(_("Cannot covert to Group because Account Type is selected."))
|
||||
throw(_("Cannot convert to Group because Account Type is selected."))
|
||||
else:
|
||||
self.is_group = 1
|
||||
self.save()
|
||||
|
@ -249,7 +249,7 @@ class TestBudget(unittest.TestCase):
|
||||
|
||||
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
|
||||
if budget_against_field == "project":
|
||||
budget_against = "_Test Project"
|
||||
budget_against = frappe.db.get_value("Project", {"project_name": "_Test Project"})
|
||||
else:
|
||||
budget_against = budget_against_CC or "_Test Cost Center - _TC"
|
||||
|
||||
@ -275,7 +275,7 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again
|
||||
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
|
||||
elif budget_against_field == "project":
|
||||
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date=nowdate())
|
||||
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project=budget_against, posting_date=nowdate())
|
||||
|
||||
def make_budget(**args):
|
||||
args = frappe._dict(args)
|
||||
|
@ -391,5 +391,5 @@ def set_default_accounts(company):
|
||||
})
|
||||
|
||||
company.save()
|
||||
install_country_fixtures(company.name)
|
||||
install_country_fixtures(company.name, company.country)
|
||||
company.create_default_tax_template()
|
||||
|
@ -306,5 +306,5 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
jv.flags.ignore_mandatory = True
|
||||
jv.submit()
|
@ -1545,6 +1545,7 @@
|
||||
"fieldname": "consolidated_invoice",
|
||||
"fieldtype": "Link",
|
||||
"label": "Consolidated Sales Invoice",
|
||||
"no_copy": 1,
|
||||
"options": "Sales Invoice",
|
||||
"read_only": 1
|
||||
}
|
||||
@ -1552,7 +1553,7 @@
|
||||
"icon": "fa fa-file-text",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-01 15:03:33.800707",
|
||||
"modified": "2021-07-29 13:37:20.636171",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
|
@ -15,6 +15,7 @@ from erpnext.healthcare.doctype.lab_test_template.lab_test_template import make_
|
||||
class TestPricingRule(unittest.TestCase):
|
||||
def setUp(self):
|
||||
delete_existing_pricing_rules()
|
||||
setup_pricing_rule_data()
|
||||
|
||||
def tearDown(self):
|
||||
delete_existing_pricing_rules()
|
||||
@ -554,6 +555,8 @@ class TestPricingRule(unittest.TestCase):
|
||||
for doc in [si, si1]:
|
||||
doc.delete()
|
||||
|
||||
test_dependencies = ["Campaign"]
|
||||
|
||||
def make_pricing_rule(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@ -600,6 +603,13 @@ def make_pricing_rule(**args):
|
||||
if args.get(applicable_for):
|
||||
doc.db_set(applicable_for, args.get(applicable_for))
|
||||
|
||||
def setup_pricing_rule_data():
|
||||
if not frappe.db.exists('Campaign', '_Test Campaign'):
|
||||
frappe.get_doc({
|
||||
'doctype': 'Campaign',
|
||||
'campaign_name': '_Test Campaign',
|
||||
'name': '_Test Campaign'
|
||||
}).insert()
|
||||
|
||||
def delete_existing_pricing_rules():
|
||||
for doctype in ["Pricing Rule", "Pricing Rule Item Code",
|
||||
|
@ -168,7 +168,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
|
||||
frappe.throw(_("Invalid {0}").format(args.get(field)))
|
||||
|
||||
parent_groups = frappe.db.sql_list("""select name from `tab%s`
|
||||
where lft>=%s and rgt<=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
|
||||
where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
|
||||
|
||||
if parenttype in ["Customer Group", "Item Group", "Territory"]:
|
||||
parent_field = "parent_{0}".format(frappe.scrub(parenttype))
|
||||
|
@ -134,7 +134,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
},
|
||||
get_query_filters: {
|
||||
docstatus: 1,
|
||||
status: ["not in", ["Closed", "Completed"]],
|
||||
status: ["not in", ["Closed", "Completed", "Return Issued"]],
|
||||
company: me.frm.doc.company,
|
||||
is_return: 0
|
||||
}
|
||||
|
@ -27,6 +27,8 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category
|
||||
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import get_item_account_wise_additional_cost
|
||||
|
||||
class WarehouseMissingError(frappe.ValidationError): pass
|
||||
|
||||
form_grid_templates = {
|
||||
"items": "templates/form_grid/item_grid.html"
|
||||
}
|
||||
@ -207,8 +209,8 @@ class PurchaseInvoice(BuyingController):
|
||||
if self.update_stock and for_validate:
|
||||
for d in self.get('items'):
|
||||
if not d.warehouse:
|
||||
frappe.throw(_("Warehouse required at Row No {0}, please set default warehouse for the item {1} for the company {2}").
|
||||
format(d.idx, d.item_code, self.company))
|
||||
frappe.throw(_("Row No {0}: Warehouse is required. Please set a Default Warehouse for Item {1} and Company {2}").
|
||||
format(d.idx, d.item_code, self.company), exc=WarehouseMissingError)
|
||||
|
||||
super(PurchaseInvoice, self).validate_warehouse()
|
||||
|
||||
@ -246,7 +248,7 @@ class PurchaseInvoice(BuyingController):
|
||||
and (not item.po_detail or
|
||||
not frappe.db.get_value("Purchase Order Item", item.po_detail, "delivered_by_supplier")):
|
||||
|
||||
if self.update_stock and (not item.from_warehouse):
|
||||
if self.update_stock and item.warehouse and (not item.from_warehouse):
|
||||
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
|
||||
msg = _("Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account").format(
|
||||
item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]), frappe.bold(item.expense_account), frappe.bold(item.warehouse))
|
||||
@ -657,7 +659,7 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": self.get_company_default("exchange_gain_loss_account"),
|
||||
"account": self.get_company_default("exchange_gain_loss_account"),
|
||||
"against": self.supplier,
|
||||
"credit": discrepancy_caused_by_exchange_rate_difference,
|
||||
"cost_center": item.cost_center,
|
||||
@ -1193,7 +1195,7 @@ def get_purchase_document_details(doc):
|
||||
purchase_receipts_or_invoices.append(item.get(doc_reference))
|
||||
if item.get(items_reference):
|
||||
items.append(item.get(items_reference))
|
||||
|
||||
|
||||
exchange_rate_map = frappe._dict(frappe.get_all(parent_doctype, filters={'name': ('in',
|
||||
purchase_receipts_or_invoices)}, fields=['name', 'conversion_rate'], as_list=1))
|
||||
|
||||
|
@ -240,7 +240,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
pi.conversion_rate = 80
|
||||
|
||||
pi.insert()
|
||||
pi.submit()
|
||||
pi.submit()
|
||||
|
||||
# Get exchnage gain and loss account
|
||||
exchange_gain_loss_account = frappe.db.get_value('Company', pi.company, 'exchange_gain_loss_account')
|
||||
@ -978,7 +978,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
unlink_enabled = frappe.db.get_value(
|
||||
"Accounts Settings", "Accounts Settings",
|
||||
"unlink_payment_on_cancel_of_invoice")
|
||||
|
||||
|
||||
frappe.db.set_value(
|
||||
"Accounts Settings", "Accounts Settings",
|
||||
"unlink_payment_on_cancel_of_invoice", 1)
|
||||
@ -1018,8 +1018,8 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account Cost for Goods Sold - _TC", 37500.0],
|
||||
["_Test Payable USD - _TC", -40000.0],
|
||||
["Exchange Gain/Loss - _TC", 2500.0]
|
||||
["_Test Payable USD - _TC", -35000.0],
|
||||
["Exchange Gain/Loss - _TC", -2500.0]
|
||||
]
|
||||
|
||||
gl_entries = frappe.db.sql("""
|
||||
@ -1027,7 +1027,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
where voucher_no=%s
|
||||
group by account
|
||||
order by account asc""", (pi.name), as_dict=1)
|
||||
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_gle[i][0], gle.account)
|
||||
self.assertEqual(expected_gle[i][1], gle.balance)
|
||||
@ -1049,8 +1049,8 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account Cost for Goods Sold - _TC", 36500.0],
|
||||
["_Test Payable USD - _TC", -38000.0],
|
||||
["Exchange Gain/Loss - _TC", 1500.0]
|
||||
["_Test Payable USD - _TC", -35000.0],
|
||||
["Exchange Gain/Loss - _TC", -1500.0]
|
||||
]
|
||||
|
||||
gl_entries = frappe.db.sql("""
|
||||
|
@ -2,13 +2,14 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import frappe, erpnext
|
||||
|
||||
import unittest, copy, time
|
||||
from frappe.utils import nowdate, flt, getdate, cint, add_days, add_months
|
||||
from frappe.model.dynamic_links import get_dynamic_link_map
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
|
||||
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import WarehouseMissingError
|
||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||
from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data
|
||||
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
|
||||
@ -1073,7 +1074,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
def test_gle_made_when_asset_is_returned(self):
|
||||
create_asset_data()
|
||||
asset = create_asset(item_code="Macbook Pro")
|
||||
|
||||
|
||||
si = create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000)
|
||||
return_si = create_sales_invoice(is_return=1, return_against=si.name, item_code="Macbook Pro", asset=asset.name, qty=-1, rate=90000)
|
||||
|
||||
@ -1081,7 +1082,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
# Asset value is 100,000 but it was sold for 90,000, so there should be a loss of 10,000
|
||||
loss_for_si = frappe.get_all(
|
||||
"GL Entry",
|
||||
"GL Entry",
|
||||
filters = {
|
||||
"voucher_no": si.name,
|
||||
"account": disposal_account
|
||||
@ -1090,7 +1091,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
)[0]
|
||||
|
||||
loss_for_return_si = frappe.get_all(
|
||||
"GL Entry",
|
||||
"GL Entry",
|
||||
filters = {
|
||||
"voucher_no": return_si.name,
|
||||
"account": disposal_account
|
||||
@ -1836,6 +1837,89 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(target_doc.company, "_Test Company 1")
|
||||
self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
|
||||
|
||||
def test_inter_company_transaction_without_default_warehouse(self):
|
||||
"Check mapping (expense account) of inter company SI to PI in absence of default warehouse."
|
||||
# setup
|
||||
old_negative_stock = frappe.db.get_single_value("Stock Settings", "allow_negative_stock")
|
||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
|
||||
|
||||
old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled('_Test Company 1')
|
||||
frappe.local.enable_perpetual_inventory['_Test Company 1'] = 1
|
||||
|
||||
frappe.db.set_value("Company", '_Test Company 1', "stock_received_but_not_billed", "Stock Received But Not Billed - _TC1")
|
||||
frappe.db.set_value("Company", '_Test Company 1', "expenses_included_in_valuation", "Expenses Included In Valuation - _TC1")
|
||||
|
||||
|
||||
if not frappe.db.exists("Customer", "_Test Internal Customer"):
|
||||
customer = frappe.get_doc({
|
||||
"customer_group": "_Test Customer Group",
|
||||
"customer_name": "_Test Internal Customer",
|
||||
"customer_type": "Individual",
|
||||
"doctype": "Customer",
|
||||
"territory": "_Test Territory",
|
||||
"is_internal_customer": 1,
|
||||
"represents_company": "_Test Company 1"
|
||||
})
|
||||
|
||||
customer.append("companies", {
|
||||
"company": "Wind Power LLC"
|
||||
})
|
||||
|
||||
customer.insert()
|
||||
|
||||
if not frappe.db.exists("Supplier", "_Test Internal Supplier"):
|
||||
supplier = frappe.get_doc({
|
||||
"supplier_group": "_Test Supplier Group",
|
||||
"supplier_name": "_Test Internal Supplier",
|
||||
"doctype": "Supplier",
|
||||
"is_internal_supplier": 1,
|
||||
"represents_company": "Wind Power LLC"
|
||||
})
|
||||
|
||||
supplier.append("companies", {
|
||||
"company": "_Test Company 1"
|
||||
})
|
||||
|
||||
supplier.insert()
|
||||
|
||||
# begin test
|
||||
si = create_sales_invoice(
|
||||
company = "Wind Power LLC",
|
||||
customer = "_Test Internal Customer",
|
||||
debit_to = "Debtors - WP",
|
||||
warehouse = "Stores - WP",
|
||||
income_account = "Sales - WP",
|
||||
expense_account = "Cost of Goods Sold - WP",
|
||||
cost_center = "Main - WP",
|
||||
currency = "USD",
|
||||
update_stock = 1,
|
||||
do_not_save = 1
|
||||
)
|
||||
si.selling_price_list = "_Test Price List Rest of the World"
|
||||
si.submit()
|
||||
|
||||
target_doc = make_inter_company_transaction("Sales Invoice", si.name)
|
||||
|
||||
# in absence of warehouse Stock Received But Not Billed is set as expense account while mapping
|
||||
# mapping is not obstructed
|
||||
self.assertIsNone(target_doc.items[0].warehouse)
|
||||
self.assertEqual(target_doc.items[0].expense_account, "Stock Received But Not Billed - _TC1")
|
||||
|
||||
target_doc.items[0].update({"cost_center": "Main - _TC1"})
|
||||
|
||||
# missing warehouse is validated on save, after mapping
|
||||
self.assertRaises(WarehouseMissingError, target_doc.save)
|
||||
|
||||
target_doc.items[0].update({"warehouse": "Stores - _TC1"})
|
||||
target_doc.save()
|
||||
|
||||
# after warehouse is set, linked account or default inventory account is set
|
||||
self.assertEqual(target_doc.items[0].expense_account, 'Stock In Hand - _TC1')
|
||||
|
||||
# tear down
|
||||
frappe.local.enable_perpetual_inventory['_Test Company 1'] = old_perpetual_inventory
|
||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock)
|
||||
|
||||
def test_internal_transfer_gl_entry(self):
|
||||
## Create internal transfer account
|
||||
account = create_account(account_name="Unrealized Profit",
|
||||
|
@ -0,0 +1,34 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "account",
|
||||
"creation": "2021-07-08 22:04:24.634967",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"account"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_in_quick_entry": 1,
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_preview": 1,
|
||||
"label": "Account",
|
||||
"options": "Account"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-08 22:35:33.202911",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "South Africa VAT Account",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class SouthAfricaVATAccount(Document):
|
||||
pass
|
@ -1,263 +1,151 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "Prompt",
|
||||
"beta": 0,
|
||||
"creation": "2018-04-13 18:42:06.431683",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "Prompt",
|
||||
"creation": "2018-04-13 18:42:06.431683",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"category_details_section",
|
||||
"category_name",
|
||||
"round_off_tax_amount",
|
||||
"column_break_2",
|
||||
"consider_party_ledger_amount",
|
||||
"tax_on_excess_amount",
|
||||
"section_break_8",
|
||||
"rates",
|
||||
"section_break_7",
|
||||
"accounts"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "category_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Category Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Tax Withholding Rates",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "rates",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Rates",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Tax Withholding Rate",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Account Details",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "accounts",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Accounts",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Tax Withholding Account",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldname": "accounts",
|
||||
"fieldtype": "Table",
|
||||
"label": "Accounts",
|
||||
"options": "Tax Withholding Account",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "category_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Category Details",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Even invoices with apply tax withholding unchecked will be considered for checking cumulative threshold breach",
|
||||
"fieldname": "consider_party_ledger_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Consider Entire Party Ledger Amount",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Tax will be withheld only for amount exceeding the cumulative threshold",
|
||||
"fieldname": "tax_on_excess_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Only Deduct Tax On Excess Amount ",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"description": "Checking this will round off the tax amount to the nearest integer",
|
||||
"fieldname": "round_off_tax_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Round Off Tax Amount",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-07-17 22:53:26.193179",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Withholding Category",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-27 21:47:34.396071",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Withholding Category",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt, getdate
|
||||
from frappe.utils import flt, getdate, cint
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
class TaxWithholdingCategory(Document):
|
||||
@ -86,7 +86,10 @@ def get_tax_withholding_details(tax_withholding_category, fiscal_year, company):
|
||||
"rate": tax_rate_detail.tax_withholding_rate,
|
||||
"threshold": tax_rate_detail.single_threshold,
|
||||
"cumulative_threshold": tax_rate_detail.cumulative_threshold,
|
||||
"description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category
|
||||
"description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category,
|
||||
"consider_party_ledger_amount": tax_withholding.consider_party_ledger_amount,
|
||||
"tax_on_excess_amount": tax_withholding.tax_on_excess_amount,
|
||||
"round_off_tax_amount": tax_withholding.round_off_tax_amount
|
||||
})
|
||||
|
||||
def get_tax_withholding_rates(tax_withholding, fiscal_year):
|
||||
@ -145,6 +148,7 @@ def get_lower_deduction_certificate(fiscal_year, pan_no):
|
||||
def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None):
|
||||
fiscal_year = fiscal_year_details[0]
|
||||
|
||||
|
||||
vouchers = get_invoice_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
|
||||
advance_vouchers = get_advance_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
|
||||
taxable_vouchers = vouchers + advance_vouchers
|
||||
@ -235,10 +239,18 @@ def get_deducted_tax(taxable_vouchers, fiscal_year, tax_details):
|
||||
|
||||
def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers):
|
||||
tds_amount = 0
|
||||
invoice_filters = {
|
||||
'name': ('in', vouchers),
|
||||
'docstatus': 1
|
||||
}
|
||||
|
||||
supp_credit_amt = frappe.db.get_value('Purchase Invoice', {
|
||||
'name': ('in', vouchers), 'docstatus': 1, 'apply_tds': 1
|
||||
}, 'sum(net_total)') or 0.0
|
||||
field = 'sum(net_total)'
|
||||
|
||||
if not cint(tax_details.consider_party_ledger_amount):
|
||||
invoice_filters.update({'apply_tds': 1})
|
||||
field = 'sum(grand_total)'
|
||||
|
||||
supp_credit_amt = frappe.db.get_value('Purchase Invoice', invoice_filters, field) or 0.0
|
||||
|
||||
supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', {
|
||||
'parent': ('in', vouchers), 'docstatus': 1,
|
||||
@ -255,6 +267,13 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu
|
||||
cumulative_threshold = tax_details.get('cumulative_threshold', 0)
|
||||
|
||||
if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
|
||||
if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(tax_details.tax_on_excess_amount):
|
||||
# Get net total again as TDS is calculated on net total
|
||||
# Grand is used to just check for threshold breach
|
||||
net_total = frappe.db.get_value('Purchase Invoice', invoice_filters, 'sum(net_total)') or 0.0
|
||||
net_total += inv.net_total
|
||||
supp_credit_amt = net_total - cumulative_threshold
|
||||
|
||||
if ldc and is_valid_certificate(
|
||||
ldc.valid_from, ldc.valid_upto,
|
||||
inv.get('posting_date') or inv.get('transaction_date'), tax_deducted,
|
||||
@ -263,6 +282,9 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu
|
||||
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
|
||||
else:
|
||||
tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0
|
||||
|
||||
if cint(tax_details.round_off_tax_amount):
|
||||
tds_amount = round(tds_amount)
|
||||
|
||||
return tds_amount
|
||||
|
||||
|
@ -87,6 +87,31 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
for d in invoices:
|
||||
d.cancel()
|
||||
|
||||
def test_tax_withholding_category_checks(self):
|
||||
invoices = []
|
||||
frappe.db.set_value("Supplier", "Test TDS Supplier3", "tax_withholding_category", "New TDS Category")
|
||||
|
||||
# First Invoice with no tds check
|
||||
pi = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000, do_not_save=True)
|
||||
pi.apply_tds = 0
|
||||
pi.save()
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
# Second Invoice will apply TDS checked
|
||||
pi1 = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000)
|
||||
pi1.submit()
|
||||
invoices.append(pi1)
|
||||
|
||||
# Cumulative threshold is 30000
|
||||
# Threshold calculation should be on both the invoices
|
||||
# TDS should be applied only on 1000
|
||||
self.assertEqual(pi1.taxes[0].tax_amount, 1000)
|
||||
|
||||
for d in invoices:
|
||||
d.cancel()
|
||||
|
||||
|
||||
def test_cumulative_threshold_tcs(self):
|
||||
frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS")
|
||||
invoices = []
|
||||
@ -195,7 +220,7 @@ def create_sales_invoice(**args):
|
||||
|
||||
def create_records():
|
||||
# create a new suppliers
|
||||
for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']:
|
||||
for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3']:
|
||||
if frappe.db.exists('Supplier', name):
|
||||
continue
|
||||
|
||||
@ -311,3 +336,23 @@ def create_tax_with_holding_category():
|
||||
'account': 'TDS - _TC'
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
if not frappe.db.exists("Tax Withholding Category", "New TDS Category"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Tax Withholding Category",
|
||||
"name": "New TDS Category",
|
||||
"category_name": "New TDS Category",
|
||||
"round_off_tax_amount": 1,
|
||||
"consider_party_ledger_amount": 1,
|
||||
"tax_on_excess_amount": 1,
|
||||
"rates": [{
|
||||
'fiscal_year': fiscal_year,
|
||||
'tax_withholding_rate': 10,
|
||||
'single_threshold': 0,
|
||||
'cumulative_threshold': 30000
|
||||
}],
|
||||
"accounts": [{
|
||||
'company': '_Test Company',
|
||||
'account': 'TDS - _TC'
|
||||
}]
|
||||
}).insert()
|
||||
|
@ -241,6 +241,7 @@ class GrossProfitGenerator(object):
|
||||
sle.voucher_detail_no == row.item_row:
|
||||
previous_stock_value = len(my_sle) > i+1 and \
|
||||
flt(my_sle[i+1].stock_value) or 0.0
|
||||
|
||||
if previous_stock_value:
|
||||
return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
|
||||
else:
|
||||
@ -335,7 +336,7 @@ class GrossProfitGenerator(object):
|
||||
res = frappe.db.sql("""select item_code, voucher_type, voucher_no,
|
||||
voucher_detail_no, stock_value, warehouse, actual_qty as qty
|
||||
from `tabStock Ledger Entry`
|
||||
where company=%(company)s
|
||||
where company=%(company)s and is_cancelled = 0
|
||||
order by
|
||||
item_code desc, warehouse desc, posting_date desc,
|
||||
posting_time desc, creation desc""", self.filters, as_dict=True)
|
||||
|
@ -566,10 +566,10 @@ def remove_ref_doc_link_from_pe(ref_type, ref_no):
|
||||
frappe.msgprint(_("Payment Entries {0} are un-linked").format("\n".join(linked_pe)))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_company_default(company, fieldname):
|
||||
value = frappe.get_cached_value('Company', company, fieldname)
|
||||
def get_company_default(company, fieldname, ignore_validation=False):
|
||||
value = frappe.get_cached_value('Company', company, fieldname)
|
||||
|
||||
if not value:
|
||||
if not ignore_validation and not value:
|
||||
throw(_("Please set default {0} in Company {1}")
|
||||
.format(frappe.get_meta("Company").get_label(fieldname), company))
|
||||
|
||||
|
39
erpnext/change_log/v13/v13_8_0.md
Normal file
39
erpnext/change_log/v13/v13_8_0.md
Normal file
@ -0,0 +1,39 @@
|
||||
# Version 13.8.0 Release Notes
|
||||
|
||||
### Features & Enhancements
|
||||
- Report to show COGS by item groups ([#26222](https://github.com/frappe/erpnext/pull/26222))
|
||||
- Enhancements in TDS ([#26677](https://github.com/frappe/erpnext/pull/26677))
|
||||
- API Endpoint to update halted Razorpay subscriptions ([#26564](https://github.com/frappe/erpnext/pull/26564))
|
||||
|
||||
### Fixes
|
||||
- Incorrect bom name ([#26600](https://github.com/frappe/erpnext/pull/26600))
|
||||
- Exchange rate revaluation posting date and precision fixes ([#26651](https://github.com/frappe/erpnext/pull/26651))
|
||||
- POS item cart dom updates ([#26460](https://github.com/frappe/erpnext/pull/26460))
|
||||
- General Ledger report not working with filter group by ([#26439](https://github.com/frappe/erpnext/pull/26438))
|
||||
- Tax calculation for Recurring additional salary ([#24206](https://github.com/frappe/erpnext/pull/24206))
|
||||
- Validation check for batch for stock reconciliation type in stock entry ([#26487](https://github.com/frappe/erpnext/pull/26487))
|
||||
- Improved UX for additional discount field ([#26502](https://github.com/frappe/erpnext/pull/26502))
|
||||
- Add missing cess amount in GSTR-3B report ([#26644](https://github.com/frappe/erpnext/pull/26644))
|
||||
- Optimized code for reposting item valuation ([#26431](https://github.com/frappe/erpnext/pull/26431))
|
||||
- FG item not fetched in manufacture entry ([#26508](https://github.com/frappe/erpnext/pull/26508))
|
||||
- Errors on parallel requests creation of company for India ([#26420](https://github.com/frappe/erpnext/pull/26420))
|
||||
- Incorrect valuation rate calculation in gross profit report ([#26558](https://github.com/frappe/erpnext/pull/26558))
|
||||
- Empty "against account" in Purchase Receipt GLE ([#26712](https://github.com/frappe/erpnext/pull/26712))
|
||||
- Remove cancelled entries from Stock and Account Value comparison report ([#26721](https://github.com/frappe/erpnext/pull/26721))
|
||||
- Remove manual permission checking ([#26691](https://github.com/frappe/erpnext/pull/26691))
|
||||
- Delete child docs when parent doc is deleted ([#26518](https://github.com/frappe/erpnext/pull/26518))
|
||||
- GST Reports timeout issue ([#26646](https://github.com/frappe/erpnext/pull/26646))
|
||||
- Parent condition in pricing rules ([#26727](https://github.com/frappe/erpnext/pull/26727))
|
||||
- Added Company filters for Loan ([#26294](https://github.com/frappe/erpnext/pull/26294))
|
||||
- Incorrect discount amount on amended document ([#26292](https://github.com/frappe/erpnext/pull/26292))
|
||||
- Exchange gain loss not set for advances linked with invoices ([#26436](https://github.com/frappe/erpnext/pull/26436))
|
||||
- Unallocated amount in Payment Entry after taxes ([#26412](https://github.com/frappe/erpnext/pull/26412))
|
||||
- Wrong operation time in Work Order ([#26613](https://github.com/frappe/erpnext/pull/26613))
|
||||
- Serial No and Batch validation ([#26614](https://github.com/frappe/erpnext/pull/26614))
|
||||
- Gl Entries for exchange gain loss ([#26734](https://github.com/frappe/erpnext/pull/26734))
|
||||
- TDS computation summary shows cancelled invoices ([#26485](https://github.com/frappe/erpnext/pull/26485))
|
||||
- Price List rate not fetched for return sales invoice fixed ([#26560](https://github.com/frappe/erpnext/pull/26560))
|
||||
- Included company in link document type filters for contact ([#26576](https://github.com/frappe/erpnext/pull/26576))
|
||||
- Ignore mandatory fields while creating payment reconciliation Journal Entry ([#26643](https://github.com/frappe/erpnext/pull/26643))
|
||||
- Unable to download GSTR-1 json ([#26418](https://github.com/frappe/erpnext/pull/26418))
|
||||
- Paging buttons not working on item group portal page ([#26498](https://github.com/frappe/erpnext/pull/26498))
|
@ -674,19 +674,24 @@ class AccountsController(TransactionBase):
|
||||
if self.get('doctype') in ['Purchase Invoice', 'Sales Invoice']:
|
||||
for d in self.get("advances"):
|
||||
if d.exchange_gain_loss:
|
||||
party = self.supplier if self.get('doctype') == 'Purchase Invoice' else self.customer
|
||||
party_account = self.credit_to if self.get('doctype') == 'Purchase Invoice' else self.debit_to
|
||||
party_type = "Supplier" if self.get('doctype') == 'Purchase Invoice' else "Customer"
|
||||
is_purchase_invoice = self.get('doctype') == 'Purchase Invoice'
|
||||
party = self.supplier if is_purchase_invoice else self.customer
|
||||
party_account = self.credit_to if is_purchase_invoice else self.debit_to
|
||||
party_type = "Supplier" if is_purchase_invoice else "Customer"
|
||||
|
||||
gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account')
|
||||
if not gain_loss_account:
|
||||
frappe.throw(_("Please set Default Exchange Gain/Loss Account in Company {}")
|
||||
.format(self.get('company')))
|
||||
account_currency = get_account_currency(gain_loss_account)
|
||||
if account_currency != self.company_currency:
|
||||
frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency))
|
||||
frappe.throw(_("Currency for {0} must be {1}").format(gain_loss_account, self.company_currency))
|
||||
|
||||
# for purchase
|
||||
dr_or_cr = 'debit' if d.exchange_gain_loss > 0 else 'credit'
|
||||
# just reverse for sales?
|
||||
dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
|
||||
if not is_purchase_invoice:
|
||||
# just reverse for sales?
|
||||
dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
@ -904,9 +909,9 @@ class AccountsController(TransactionBase):
|
||||
frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
|
||||
.format(item.item_code, item.idx, max_allowed_amt))
|
||||
|
||||
def get_company_default(self, fieldname):
|
||||
def get_company_default(self, fieldname, ignore_validation=False):
|
||||
from erpnext.accounts.utils import get_company_default
|
||||
return get_company_default(self.company, fieldname)
|
||||
return get_company_default(self.company, fieldname, ignore_validation=ignore_validation)
|
||||
|
||||
def get_stock_items(self):
|
||||
stock_items = []
|
||||
|
@ -407,6 +407,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
INNER JOIN `tabBatch` batch on sle.batch_no = batch.name
|
||||
where
|
||||
batch.disabled = 0
|
||||
and sle.is_cancelled = 0
|
||||
and sle.item_code = %(item_code)s
|
||||
and sle.warehouse = %(warehouse)s
|
||||
and (sle.batch_no like %(txt)s
|
||||
|
@ -53,6 +53,13 @@ frappe.ui.form.on("Opportunity", {
|
||||
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
|
||||
},
|
||||
|
||||
status:function(frm){
|
||||
if (frm.doc.status == "Lost"){
|
||||
frm.trigger('set_as_lost_dialog');
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
customer_address: function(frm, cdt, cdn) {
|
||||
erpnext.utils.get_address_display(frm, 'customer_address', 'address_display', false);
|
||||
},
|
||||
@ -91,11 +98,6 @@ frappe.ui.form.on("Opportunity", {
|
||||
frm.add_custom_button(__('Quotation'),
|
||||
cur_frm.cscript.create_quotation, __('Create'));
|
||||
|
||||
if(doc.status!=="Quotation") {
|
||||
frm.add_custom_button(__('Lost'), () => {
|
||||
frm.trigger('set_as_lost_dialog');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(!frm.doc.__islocal && frm.perm[0].write && frm.doc.docstatus==0) {
|
||||
|
@ -34,11 +34,14 @@ def enroll_student(source_name):
|
||||
}
|
||||
}}, ignore_permissions=True)
|
||||
student.save()
|
||||
|
||||
student_applicant = frappe.db.get_value("Student Applicant", source_name,
|
||||
["student_category", "program"], as_dict=True)
|
||||
program_enrollment = frappe.new_doc("Program Enrollment")
|
||||
program_enrollment.student = student.name
|
||||
program_enrollment.student_category = student.student_category
|
||||
program_enrollment.student_category = student_applicant.student_category
|
||||
program_enrollment.student_name = student.title
|
||||
program_enrollment.program = frappe.db.get_value("Student Applicant", source_name, "program")
|
||||
program_enrollment.program = student_applicant.program
|
||||
frappe.publish_realtime('enroll_student_progress', {"progress": [2, 4]}, user=frappe.session.user)
|
||||
return program_enrollment
|
||||
|
||||
|
@ -1,195 +1,68 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2016-06-10 03:29:02.539914",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"actions": [],
|
||||
"creation": "2016-06-10 03:29:02.539914",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"student_applicant",
|
||||
"student",
|
||||
"student_name",
|
||||
"column_break_3",
|
||||
"student_batch_name",
|
||||
"student_category"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "student_applicant",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Student Applicant",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Student Applicant",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "student_applicant",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Student Applicant",
|
||||
"options": "Student Applicant"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "student",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Student",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Student",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "student",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Student",
|
||||
"options": "Student"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "student_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Student Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "student_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Student Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "student_batch_name",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Student Batch Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Student Batch Name",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"fieldname": "student_batch_name",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Student Batch Name",
|
||||
"options": "Student Batch Name"
|
||||
},
|
||||
{
|
||||
"fieldname": "student_category",
|
||||
"fieldtype": "Link",
|
||||
"label": "Student Category",
|
||||
"options": "Student Category",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-01-02 12:03:53.890741",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Program Enrollment Tool Student",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"restrict_to_domain": "Education",
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-29 18:19:54.471594",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Program Enrollment Tool Student",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"restrict_to_domain": "Education",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
@ -1,353 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
import json
|
||||
from frappe.utils import cstr, cint, nowdate, getdate, flt, get_request_session, get_datetime
|
||||
from erpnext.erpnext_integrations.utils import validate_webhooks_request
|
||||
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note, make_sales_invoice
|
||||
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import sync_item_from_shopify
|
||||
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer
|
||||
from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log, dump_request_data
|
||||
from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import get_shopify_url, get_header
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@validate_webhooks_request("Shopify Settings", 'X-Shopify-Hmac-Sha256', secret_key='shared_secret')
|
||||
def store_request_data(order=None, event=None):
|
||||
if frappe.request:
|
||||
order = json.loads(frappe.request.data)
|
||||
event = frappe.request.headers.get('X-Shopify-Topic')
|
||||
|
||||
dump_request_data(order, event)
|
||||
|
||||
def sync_sales_order(order, request_id=None, old_order_sync=False):
|
||||
frappe.set_user('Administrator')
|
||||
shopify_settings = frappe.get_doc("Shopify Settings")
|
||||
frappe.flags.request_id = request_id
|
||||
|
||||
if not frappe.db.get_value("Sales Order", filters={"shopify_order_id": cstr(order['id'])}):
|
||||
try:
|
||||
validate_customer(order, shopify_settings)
|
||||
validate_item(order, shopify_settings)
|
||||
create_order(order, shopify_settings, old_order_sync=old_order_sync)
|
||||
except Exception as e:
|
||||
make_shopify_log(status="Error", exception=e)
|
||||
|
||||
else:
|
||||
make_shopify_log(status="Success")
|
||||
|
||||
def prepare_sales_invoice(order, request_id=None):
|
||||
frappe.set_user('Administrator')
|
||||
shopify_settings = frappe.get_doc("Shopify Settings")
|
||||
frappe.flags.request_id = request_id
|
||||
|
||||
try:
|
||||
sales_order = get_sales_order(cstr(order['id']))
|
||||
if sales_order:
|
||||
create_sales_invoice(order, shopify_settings, sales_order)
|
||||
make_shopify_log(status="Success")
|
||||
except Exception as e:
|
||||
make_shopify_log(status="Error", exception=e, rollback=True)
|
||||
|
||||
def prepare_delivery_note(order, request_id=None):
|
||||
frappe.set_user('Administrator')
|
||||
shopify_settings = frappe.get_doc("Shopify Settings")
|
||||
frappe.flags.request_id = request_id
|
||||
|
||||
try:
|
||||
sales_order = get_sales_order(cstr(order['id']))
|
||||
if sales_order:
|
||||
create_delivery_note(order, shopify_settings, sales_order)
|
||||
make_shopify_log(status="Success")
|
||||
except Exception as e:
|
||||
make_shopify_log(status="Error", exception=e, rollback=True)
|
||||
|
||||
def get_sales_order(shopify_order_id):
|
||||
sales_order = frappe.db.get_value("Sales Order", filters={"shopify_order_id": shopify_order_id})
|
||||
if sales_order:
|
||||
so = frappe.get_doc("Sales Order", sales_order)
|
||||
return so
|
||||
|
||||
def validate_customer(order, shopify_settings):
|
||||
customer_id = order.get("customer", {}).get("id")
|
||||
if customer_id:
|
||||
if not frappe.db.get_value("Customer", {"shopify_customer_id": customer_id}, "name"):
|
||||
create_customer(order.get("customer"), shopify_settings)
|
||||
|
||||
def validate_item(order, shopify_settings):
|
||||
for item in order.get("line_items"):
|
||||
if item.get("product_id") and not frappe.db.get_value("Item", {"shopify_product_id": item.get("product_id")}, "name"):
|
||||
sync_item_from_shopify(shopify_settings, item)
|
||||
|
||||
def create_order(order, shopify_settings, old_order_sync=False, company=None):
|
||||
so = create_sales_order(order, shopify_settings, company)
|
||||
if so:
|
||||
if order.get("financial_status") == "paid":
|
||||
create_sales_invoice(order, shopify_settings, so, old_order_sync=old_order_sync)
|
||||
|
||||
if order.get("fulfillments") and not old_order_sync:
|
||||
create_delivery_note(order, shopify_settings, so)
|
||||
|
||||
def create_sales_order(shopify_order, shopify_settings, company=None):
|
||||
product_not_exists = []
|
||||
customer = frappe.db.get_value("Customer", {"shopify_customer_id": shopify_order.get("customer", {}).get("id")}, "name")
|
||||
so = frappe.db.get_value("Sales Order", {"shopify_order_id": shopify_order.get("id")}, "name")
|
||||
|
||||
if not so:
|
||||
items = get_order_items(shopify_order.get("line_items"), shopify_settings, getdate(shopify_order.get('created_at')))
|
||||
|
||||
if not items:
|
||||
message = 'Following items exists in the shopify order but relevant records were not found in the shopify Product master'
|
||||
message += "\n" + ", ".join(product_not_exists)
|
||||
|
||||
make_shopify_log(status="Error", exception=message, rollback=True)
|
||||
|
||||
return ''
|
||||
|
||||
so = frappe.get_doc({
|
||||
"doctype": "Sales Order",
|
||||
"naming_series": shopify_settings.sales_order_series or "SO-Shopify-",
|
||||
"shopify_order_id": shopify_order.get("id"),
|
||||
"shopify_order_number": shopify_order.get("name"),
|
||||
"customer": customer or shopify_settings.default_customer,
|
||||
"transaction_date": getdate(shopify_order.get("created_at")) or nowdate(),
|
||||
"delivery_date": getdate(shopify_order.get("created_at")) or nowdate(),
|
||||
"company": shopify_settings.company,
|
||||
"selling_price_list": shopify_settings.price_list,
|
||||
"ignore_pricing_rule": 1,
|
||||
"items": items,
|
||||
"taxes": get_order_taxes(shopify_order, shopify_settings),
|
||||
"apply_discount_on": "Grand Total",
|
||||
"discount_amount": get_discounted_amount(shopify_order),
|
||||
})
|
||||
|
||||
if company:
|
||||
so.update({
|
||||
"company": company,
|
||||
"status": "Draft"
|
||||
})
|
||||
so.flags.ignore_mandatory = True
|
||||
so.save(ignore_permissions=True)
|
||||
so.submit()
|
||||
|
||||
else:
|
||||
so = frappe.get_doc("Sales Order", so)
|
||||
|
||||
frappe.db.commit()
|
||||
return so
|
||||
|
||||
def create_sales_invoice(shopify_order, shopify_settings, so, old_order_sync=False):
|
||||
if not frappe.db.get_value("Sales Invoice", {"shopify_order_id": shopify_order.get("id")}, "name")\
|
||||
and so.docstatus==1 and not so.per_billed and cint(shopify_settings.sync_sales_invoice):
|
||||
|
||||
if old_order_sync:
|
||||
posting_date = getdate(shopify_order.get('created_at'))
|
||||
else:
|
||||
posting_date = nowdate()
|
||||
|
||||
si = make_sales_invoice(so.name, ignore_permissions=True)
|
||||
si.shopify_order_id = shopify_order.get("id")
|
||||
si.shopify_order_number = shopify_order.get("name")
|
||||
si.set_posting_time = 1
|
||||
si.posting_date = posting_date
|
||||
si.due_date = posting_date
|
||||
si.naming_series = shopify_settings.sales_invoice_series or "SI-Shopify-"
|
||||
si.flags.ignore_mandatory = True
|
||||
set_cost_center(si.items, shopify_settings.cost_center)
|
||||
si.insert(ignore_mandatory=True)
|
||||
si.submit()
|
||||
make_payament_entry_against_sales_invoice(si, shopify_settings, posting_date)
|
||||
frappe.db.commit()
|
||||
|
||||
def set_cost_center(items, cost_center):
|
||||
for item in items:
|
||||
item.cost_center = cost_center
|
||||
|
||||
def make_payament_entry_against_sales_invoice(doc, shopify_settings, posting_date=None):
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
payment_entry = get_payment_entry(doc.doctype, doc.name, bank_account=shopify_settings.cash_bank_account)
|
||||
payment_entry.flags.ignore_mandatory = True
|
||||
payment_entry.reference_no = doc.name
|
||||
payment_entry.posting_date = posting_date or nowdate()
|
||||
payment_entry.reference_date = posting_date or nowdate()
|
||||
payment_entry.insert(ignore_permissions=True)
|
||||
payment_entry.submit()
|
||||
|
||||
def create_delivery_note(shopify_order, shopify_settings, so):
|
||||
if not cint(shopify_settings.sync_delivery_note):
|
||||
return
|
||||
|
||||
for fulfillment in shopify_order.get("fulfillments"):
|
||||
if not frappe.db.get_value("Delivery Note", {"shopify_fulfillment_id": fulfillment.get("id")}, "name")\
|
||||
and so.docstatus==1:
|
||||
|
||||
dn = make_delivery_note(so.name)
|
||||
dn.shopify_order_id = fulfillment.get("order_id")
|
||||
dn.shopify_order_number = shopify_order.get("name")
|
||||
dn.set_posting_time = 1
|
||||
dn.posting_date = getdate(fulfillment.get("created_at"))
|
||||
dn.shopify_fulfillment_id = fulfillment.get("id")
|
||||
dn.naming_series = shopify_settings.delivery_note_series or "DN-Shopify-"
|
||||
dn.items = get_fulfillment_items(dn.items, fulfillment.get("line_items"), shopify_settings)
|
||||
dn.flags.ignore_mandatory = True
|
||||
dn.save()
|
||||
dn.submit()
|
||||
frappe.db.commit()
|
||||
|
||||
def get_fulfillment_items(dn_items, fulfillment_items, shopify_settings):
|
||||
return [dn_item.update({"qty": item.get("quantity")}) for item in fulfillment_items for dn_item in dn_items\
|
||||
if get_item_code(item) == dn_item.item_code]
|
||||
|
||||
def get_discounted_amount(order):
|
||||
discounted_amount = 0.0
|
||||
for discount in order.get("discount_codes"):
|
||||
discounted_amount += flt(discount.get("amount"))
|
||||
return discounted_amount
|
||||
|
||||
def get_order_items(order_items, shopify_settings, delivery_date):
|
||||
items = []
|
||||
all_product_exists = True
|
||||
product_not_exists = []
|
||||
|
||||
for shopify_item in order_items:
|
||||
if not shopify_item.get('product_exists'):
|
||||
all_product_exists = False
|
||||
product_not_exists.append({'title':shopify_item.get('title'),
|
||||
'shopify_order_id': shopify_item.get('id')})
|
||||
continue
|
||||
|
||||
if all_product_exists:
|
||||
item_code = get_item_code(shopify_item)
|
||||
items.append({
|
||||
"item_code": item_code,
|
||||
"item_name": shopify_item.get("name"),
|
||||
"rate": shopify_item.get("price"),
|
||||
"delivery_date": delivery_date,
|
||||
"qty": shopify_item.get("quantity"),
|
||||
"stock_uom": shopify_item.get("uom") or _("Nos"),
|
||||
"warehouse": shopify_settings.warehouse
|
||||
})
|
||||
else:
|
||||
items = []
|
||||
|
||||
return items
|
||||
|
||||
def get_item_code(shopify_item):
|
||||
item_code = frappe.db.get_value("Item", {"shopify_variant_id": shopify_item.get("variant_id")}, "item_code")
|
||||
if not item_code:
|
||||
item_code = frappe.db.get_value("Item", {"shopify_product_id": shopify_item.get("product_id")}, "item_code")
|
||||
if not item_code:
|
||||
item_code = frappe.db.get_value("Item", {"item_name": shopify_item.get("title")}, "item_code")
|
||||
|
||||
return item_code
|
||||
|
||||
def get_order_taxes(shopify_order, shopify_settings):
|
||||
taxes = []
|
||||
for tax in shopify_order.get("tax_lines"):
|
||||
taxes.append({
|
||||
"charge_type": _("On Net Total"),
|
||||
"account_head": get_tax_account_head(tax),
|
||||
"description": "{0} - {1}%".format(tax.get("title"), tax.get("rate") * 100.0),
|
||||
"rate": tax.get("rate") * 100.00,
|
||||
"included_in_print_rate": 1 if shopify_order.get("taxes_included") else 0,
|
||||
"cost_center": shopify_settings.cost_center
|
||||
})
|
||||
|
||||
taxes = update_taxes_with_shipping_lines(taxes, shopify_order.get("shipping_lines"), shopify_settings)
|
||||
|
||||
return taxes
|
||||
|
||||
def update_taxes_with_shipping_lines(taxes, shipping_lines, shopify_settings):
|
||||
"""Shipping lines represents the shipping details,
|
||||
each such shipping detail consists of a list of tax_lines"""
|
||||
for shipping_charge in shipping_lines:
|
||||
if shipping_charge.get("price"):
|
||||
taxes.append({
|
||||
"charge_type": _("Actual"),
|
||||
"account_head": get_tax_account_head(shipping_charge),
|
||||
"description": shipping_charge["title"],
|
||||
"tax_amount": shipping_charge["price"],
|
||||
"cost_center": shopify_settings.cost_center
|
||||
})
|
||||
|
||||
for tax in shipping_charge.get("tax_lines"):
|
||||
taxes.append({
|
||||
"charge_type": _("Actual"),
|
||||
"account_head": get_tax_account_head(tax),
|
||||
"description": tax["title"],
|
||||
"tax_amount": tax["price"],
|
||||
"cost_center": shopify_settings.cost_center
|
||||
})
|
||||
|
||||
return taxes
|
||||
|
||||
def get_tax_account_head(tax):
|
||||
tax_title = tax.get("title").encode("utf-8")
|
||||
|
||||
tax_account = frappe.db.get_value("Shopify Tax Account", \
|
||||
{"parent": "Shopify Settings", "shopify_tax": tax_title}, "tax_account")
|
||||
|
||||
if not tax_account:
|
||||
frappe.throw(_("Tax Account not specified for Shopify Tax {0}").format(tax.get("title")))
|
||||
|
||||
return tax_account
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def sync_old_orders():
|
||||
frappe.set_user('Administrator')
|
||||
shopify_settings = frappe.get_doc('Shopify Settings')
|
||||
|
||||
if not shopify_settings.sync_missing_orders:
|
||||
return
|
||||
|
||||
url = get_url(shopify_settings)
|
||||
session = get_request_session()
|
||||
|
||||
try:
|
||||
res = session.get(url, headers=get_header(shopify_settings))
|
||||
res.raise_for_status()
|
||||
orders = res.json()["orders"]
|
||||
|
||||
for order in orders:
|
||||
if is_sync_complete(shopify_settings, order):
|
||||
stop_sync(shopify_settings)
|
||||
return
|
||||
|
||||
sync_sales_order(order=order, old_order_sync=True)
|
||||
last_order_id = order.get('id')
|
||||
|
||||
if last_order_id:
|
||||
shopify_settings.load_from_db()
|
||||
shopify_settings.last_order_id = last_order_id
|
||||
shopify_settings.save()
|
||||
frappe.db.commit()
|
||||
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def stop_sync(shopify_settings):
|
||||
shopify_settings.sync_missing_orders = 0
|
||||
shopify_settings.last_order_id = ''
|
||||
shopify_settings.save()
|
||||
frappe.db.commit()
|
||||
|
||||
def get_url(shopify_settings):
|
||||
last_order_id = shopify_settings.last_order_id
|
||||
|
||||
if not last_order_id:
|
||||
if shopify_settings.sync_based_on == 'Date':
|
||||
url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&created_at_min={0}&since_id=0".format(
|
||||
get_datetime(shopify_settings.from_date)), shopify_settings)
|
||||
else:
|
||||
url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(
|
||||
shopify_settings.from_order_id), shopify_settings)
|
||||
else:
|
||||
url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings)
|
||||
|
||||
return url
|
||||
|
||||
def is_sync_complete(shopify_settings, order):
|
||||
if shopify_settings.sync_based_on == 'Date':
|
||||
return getdate(shopify_settings.to_date) < getdate(order.get('created_at'))
|
||||
else:
|
||||
return cstr(order.get('id')) == cstr(shopify_settings.to_order_id)
|
||||
|
@ -1,22 +0,0 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Shopify Log', {
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.request_data && frm.doc.status=='Error'){
|
||||
frm.add_custom_button('Resync', function() {
|
||||
frappe.call({
|
||||
method:"erpnext.erpnext_integrations.doctype.shopify_log.shopify_log.resync",
|
||||
args:{
|
||||
method:frm.doc.method,
|
||||
name: frm.doc.name,
|
||||
request_data: frm.doc.request_data
|
||||
},
|
||||
callback: function(r){
|
||||
frappe.msgprint(__("Order rescheduled for sync"))
|
||||
}
|
||||
})
|
||||
}).addClass('btn-primary');
|
||||
}
|
||||
}
|
||||
});
|
@ -1,268 +0,0 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2016-03-14 10:02:06.227184",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "System",
|
||||
"editable_grid": 0,
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Title",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Queued",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Status",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "method",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Method",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "message",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Message",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "traceback",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Traceback",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "request_data",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Request Data",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 1,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-04-20 16:23:36.862381",
|
||||
"modified_by": "Administrator",
|
||||
"module": "ERPNext Integrations",
|
||||
"name": "Shopify Log",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Administrator",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "title",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
from frappe.model.document import Document
|
||||
from erpnext.erpnext_integrations.utils import get_webhook_address
|
||||
|
||||
class ShopifyLog(Document):
|
||||
pass
|
||||
|
||||
|
||||
def make_shopify_log(status="Queued", exception=None, rollback=False):
|
||||
# if name not provided by log calling method then fetch existing queued state log
|
||||
make_new = False
|
||||
|
||||
if not frappe.flags.request_id:
|
||||
make_new = True
|
||||
|
||||
if rollback:
|
||||
frappe.db.rollback()
|
||||
|
||||
if make_new:
|
||||
log = frappe.get_doc({"doctype":"Shopify Log"}).insert(ignore_permissions=True)
|
||||
else:
|
||||
log = log = frappe.get_doc("Shopify Log", frappe.flags.request_id)
|
||||
|
||||
log.message = get_message(exception)
|
||||
log.traceback = frappe.get_traceback()
|
||||
log.status = status
|
||||
log.save(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
|
||||
def get_message(exception):
|
||||
message = None
|
||||
|
||||
if hasattr(exception, 'message'):
|
||||
message = exception.message
|
||||
elif hasattr(exception, '__str__'):
|
||||
message = exception.__str__()
|
||||
else:
|
||||
message = "Something went wrong while syncing"
|
||||
return message
|
||||
|
||||
def dump_request_data(data, event="create/order"):
|
||||
event_mapper = {
|
||||
"orders/create": get_webhook_address(connector_name='shopify_connection', method="sync_sales_order", exclude_uri=True),
|
||||
"orders/paid" : get_webhook_address(connector_name='shopify_connection', method="prepare_sales_invoice", exclude_uri=True),
|
||||
"orders/fulfilled": get_webhook_address(connector_name='shopify_connection', method="prepare_delivery_note", exclude_uri=True)
|
||||
}
|
||||
|
||||
log = frappe.get_doc({
|
||||
"doctype": "Shopify Log",
|
||||
"request_data": json.dumps(data, indent=1),
|
||||
"method": event_mapper[event]
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
frappe.db.commit()
|
||||
frappe.enqueue(method=event_mapper[event], queue='short', timeout=300, is_async=True,
|
||||
**{"order": data, "request_id": log.name})
|
||||
|
||||
@frappe.whitelist()
|
||||
def resync(method, name, request_data):
|
||||
frappe.db.set_value("Shopify Log", name, "status", "Queued", update_modified=False)
|
||||
frappe.enqueue(method=method, queue='short', timeout=300, is_async=True,
|
||||
**{"order": json.loads(request_data), "request_id": name})
|
@ -1,12 +0,0 @@
|
||||
frappe.listview_settings['Shopify Log'] = {
|
||||
add_fields: ["status"],
|
||||
get_indicator: function(doc) {
|
||||
if(doc.status==="Success"){
|
||||
return [__("Success"), "green", "status,=,Success"];
|
||||
} else if(doc.status ==="Error"){
|
||||
return [__("Error"), "red", "status,=,Error"];
|
||||
} else if(doc.status ==="Queued"){
|
||||
return [__("Queued"), "orange", "status,=,Queued"];
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Shopify Log", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Shopify Log
|
||||
() => frappe.tests.make('Shopify Log', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
@ -1,12 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
# test_records = frappe.get_test_records('Shopify Log')
|
||||
|
||||
class TestShopifyLog(unittest.TestCase):
|
||||
pass
|
@ -1,90 +0,0 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.provide("erpnext_integrations.shopify_settings");
|
||||
|
||||
frappe.ui.form.on("Shopify Settings", "onload", function(frm){
|
||||
frappe.call({
|
||||
method:"erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings.get_series",
|
||||
callback:function(r){
|
||||
$.each(r.message, function(key, value){
|
||||
set_field_options(key, value);
|
||||
});
|
||||
}
|
||||
});
|
||||
erpnext_integrations.shopify_settings.setup_queries(frm);
|
||||
})
|
||||
|
||||
frappe.ui.form.on("Shopify Settings", "app_type", function(frm) {
|
||||
frm.toggle_reqd("api_key", (frm.doc.app_type == "Private"));
|
||||
frm.toggle_reqd("password", (frm.doc.app_type == "Private"));
|
||||
})
|
||||
|
||||
frappe.ui.form.on("Shopify Settings", "refresh", function(frm){
|
||||
if(!frm.doc.__islocal && frm.doc.enable_shopify === 1){
|
||||
frm.toggle_reqd("price_list", true);
|
||||
frm.toggle_reqd("warehouse", true);
|
||||
frm.toggle_reqd("taxes", true);
|
||||
frm.toggle_reqd("company", true);
|
||||
frm.toggle_reqd("cost_center", true);
|
||||
frm.toggle_reqd("cash_bank_account", true);
|
||||
frm.toggle_reqd("sales_order_series", true);
|
||||
frm.toggle_reqd("customer_group", true);
|
||||
frm.toggle_reqd("shared_secret", true);
|
||||
|
||||
frm.toggle_reqd("sales_invoice_series", frm.doc.sync_sales_invoice);
|
||||
frm.toggle_reqd("delivery_note_series", frm.doc.sync_delivery_note);
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
$.extend(erpnext_integrations.shopify_settings, {
|
||||
setup_queries: function(frm) {
|
||||
frm.fields_dict["warehouse"].get_query = function(doc) {
|
||||
return {
|
||||
filters:{
|
||||
"company": doc.company,
|
||||
"is_group": "No"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frm.fields_dict["taxes"].grid.get_field("tax_account").get_query = function(doc){
|
||||
return {
|
||||
"query": "erpnext.controllers.queries.tax_account_query",
|
||||
"filters": {
|
||||
"account_type": ["Tax", "Chargeable", "Expense Account"],
|
||||
"company": doc.company
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frm.fields_dict["cash_bank_account"].get_query = function(doc) {
|
||||
return {
|
||||
filters: [
|
||||
["Account", "account_type", "in", ["Cash", "Bank"]],
|
||||
["Account", "root_type", "=", "Asset"],
|
||||
["Account", "is_group", "=",0],
|
||||
["Account", "company", "=", doc.company]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
frm.fields_dict["cost_center"].get_query = function(doc) {
|
||||
return {
|
||||
filters:{
|
||||
"company": doc.company,
|
||||
"is_group": "No"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frm.fields_dict["price_list"].get_query = function() {
|
||||
return {
|
||||
filters:{
|
||||
"selling": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
@ -1,353 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2015-05-18 05:21:07.270859",
|
||||
"doctype": "DocType",
|
||||
"document_type": "System",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"status_html",
|
||||
"enable_shopify",
|
||||
"app_type",
|
||||
"column_break_4",
|
||||
"last_sync_datetime",
|
||||
"section_break_2",
|
||||
"shopify_url",
|
||||
"api_key",
|
||||
"column_break_3",
|
||||
"password",
|
||||
"shared_secret",
|
||||
"access_token",
|
||||
"section_break_38",
|
||||
"webhooks",
|
||||
"section_break_15",
|
||||
"default_customer",
|
||||
"column_break_19",
|
||||
"customer_group",
|
||||
"company_dependent_settings",
|
||||
"company",
|
||||
"cash_bank_account",
|
||||
"column_break_20",
|
||||
"cost_center",
|
||||
"erp_settings",
|
||||
"price_list",
|
||||
"update_price_in_erpnext_price_list",
|
||||
"column_break_26",
|
||||
"warehouse",
|
||||
"section_break_25",
|
||||
"sales_order_series",
|
||||
"column_break_27",
|
||||
"sync_delivery_note",
|
||||
"delivery_note_series",
|
||||
"sync_sales_invoice",
|
||||
"sales_invoice_series",
|
||||
"section_break_22",
|
||||
"html_16",
|
||||
"taxes",
|
||||
"syncing_details_section",
|
||||
"sync_missing_orders",
|
||||
"sync_based_on",
|
||||
"column_break_41",
|
||||
"from_date",
|
||||
"to_date",
|
||||
"from_order_id",
|
||||
"to_order_id",
|
||||
"last_order_id"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "status_html",
|
||||
"fieldtype": "HTML",
|
||||
"label": "status html",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_shopify",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Shopify"
|
||||
},
|
||||
{
|
||||
"default": "Private",
|
||||
"fieldname": "app_type",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "App Type",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "last_sync_datetime",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Last Sync Datetime",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_2",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"description": "eg: frappe.myshopify.com",
|
||||
"fieldname": "shopify_url",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Shop URL",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.app_type==\"Private\"",
|
||||
"fieldname": "api_key",
|
||||
"fieldtype": "Data",
|
||||
"label": "API Key"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.app_type==\"Private\"",
|
||||
"fieldname": "password",
|
||||
"fieldtype": "Password",
|
||||
"label": "Password"
|
||||
},
|
||||
{
|
||||
"fieldname": "shared_secret",
|
||||
"fieldtype": "Data",
|
||||
"label": "Shared secret"
|
||||
},
|
||||
{
|
||||
"fieldname": "access_token",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Access Token",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_38",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Webhooks Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "webhooks",
|
||||
"fieldtype": "Table",
|
||||
"label": "Webhooks",
|
||||
"options": "Shopify Webhook Detail",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_15",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Customer Settings"
|
||||
},
|
||||
{
|
||||
"description": "If Shopify does not have a customer in the order, then while syncing the orders, the system will consider the default customer for the order",
|
||||
"fieldname": "default_customer",
|
||||
"fieldtype": "Link",
|
||||
"label": "Default Customer",
|
||||
"options": "Customer"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_19",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"description": "Customer Group will set to selected group while syncing customers from Shopify",
|
||||
"fieldname": "customer_group",
|
||||
"fieldtype": "Link",
|
||||
"label": "Customer Group",
|
||||
"options": "Customer Group"
|
||||
},
|
||||
{
|
||||
"fieldname": "company_dependent_settings",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "For Company",
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"description": "Cash Account will used for Sales Invoice creation",
|
||||
"fieldname": "cash_bank_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cash/Bank Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_20",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "erp_settings",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "price_list",
|
||||
"fieldtype": "Link",
|
||||
"label": "Price List",
|
||||
"options": "Price List"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "update_price_in_erpnext_price_list",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Price from Shopify To ERPNext Price List"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_26",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"description": "Default Warehouse to to create Sales Order and Delivery Note",
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_25",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "sales_order_series",
|
||||
"fieldtype": "Select",
|
||||
"label": "Sales Order Series"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_27",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "sync_delivery_note",
|
||||
"fieldtype": "Check",
|
||||
"label": "Import Delivery Notes from Shopify on Shipment"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.sync_delivery_note==1",
|
||||
"fieldname": "delivery_note_series",
|
||||
"fieldtype": "Select",
|
||||
"label": "Delivery Note Series"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "sync_sales_invoice",
|
||||
"fieldtype": "Check",
|
||||
"label": "Import Sales Invoice from Shopify if Payment is marked"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.sync_sales_invoice==1",
|
||||
"fieldname": "sales_invoice_series",
|
||||
"fieldtype": "Select",
|
||||
"label": "Sales Invoice Series"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_22",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "html_16",
|
||||
"fieldtype": "HTML",
|
||||
"options": "Map Shopify Taxes / Shipping Charges to ERPNext Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "taxes",
|
||||
"fieldtype": "Table",
|
||||
"label": "Shopify Tax Account",
|
||||
"options": "Shopify Tax Account"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "syncing_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Syncing Missing Orders"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.sync_missing_orders",
|
||||
"fieldname": "last_order_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "Last Order Id",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_41",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "On checking this Order from the ",
|
||||
"fieldname": "sync_missing_orders",
|
||||
"fieldtype": "Check",
|
||||
"label": "Sync Missing Old Shopify Orders"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.sync_missing_orders",
|
||||
"fieldname": "sync_based_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Sync Based On",
|
||||
"mandatory_depends_on": "eval:doc.sync_missing_orders",
|
||||
"options": "\nDate\nShopify Order Id"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders",
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "From Date",
|
||||
"mandatory_depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders",
|
||||
"fieldname": "to_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "To Date",
|
||||
"mandatory_depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders",
|
||||
"fieldname": "from_order_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "From Order Id",
|
||||
"mandatory_depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders",
|
||||
"fieldname": "to_order_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "To Order Id",
|
||||
"mandatory_depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders"
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-02 17:35:41.953317",
|
||||
"modified_by": "Administrator",
|
||||
"module": "ERPNext Integrations",
|
||||
"name": "Shopify Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -1,144 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import get_request_session
|
||||
from requests.exceptions import HTTPError
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
from erpnext.erpnext_integrations.utils import get_webhook_address
|
||||
from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log
|
||||
|
||||
class ShopifySettings(Document):
|
||||
def validate(self):
|
||||
if self.enable_shopify == 1:
|
||||
setup_custom_fields()
|
||||
self.validate_access_credentials()
|
||||
self.register_webhooks()
|
||||
else:
|
||||
self.unregister_webhooks()
|
||||
|
||||
def validate_access_credentials(self):
|
||||
if not (self.get_password(raise_exception=False) and self.api_key and self.shopify_url):
|
||||
frappe.msgprint(_("Missing value for Password, API Key or Shopify URL"), raise_exception=frappe.ValidationError)
|
||||
|
||||
def register_webhooks(self):
|
||||
webhooks = ["orders/create", "orders/paid", "orders/fulfilled"]
|
||||
# url = get_shopify_url('admin/webhooks.json', self)
|
||||
created_webhooks = [d.method for d in self.webhooks]
|
||||
url = get_shopify_url('admin/api/2021-04/webhooks.json', self)
|
||||
for method in webhooks:
|
||||
session = get_request_session()
|
||||
try:
|
||||
res = session.post(url, data=json.dumps({
|
||||
"webhook": {
|
||||
"topic": method,
|
||||
"address": get_webhook_address(connector_name='shopify_connection', method='store_request_data', force_https=True),
|
||||
"format": "json"
|
||||
}
|
||||
}), headers=get_header(self))
|
||||
res.raise_for_status()
|
||||
self.update_webhook_table(method, res.json())
|
||||
|
||||
except HTTPError as e:
|
||||
error_message = res.json().get('errors', e)
|
||||
make_shopify_log(status="Warning", exception=error_message, rollback=True)
|
||||
|
||||
except Exception as e:
|
||||
make_shopify_log(status="Warning", exception=e, rollback=True)
|
||||
|
||||
def unregister_webhooks(self):
|
||||
session = get_request_session()
|
||||
deleted_webhooks = []
|
||||
|
||||
for d in self.webhooks:
|
||||
url = get_shopify_url('admin/api/2021-04/webhooks/{0}.json'.format(d.webhook_id), self)
|
||||
try:
|
||||
res = session.delete(url, headers=get_header(self))
|
||||
res.raise_for_status()
|
||||
deleted_webhooks.append(d)
|
||||
|
||||
except HTTPError as e:
|
||||
error_message = res.json().get('errors', e)
|
||||
make_shopify_log(status="Warning", exception=error_message, rollback=True)
|
||||
|
||||
except Exception as e:
|
||||
frappe.log_error(message=e, title='Shopify Webhooks Issue')
|
||||
|
||||
for d in deleted_webhooks:
|
||||
self.remove(d)
|
||||
|
||||
def update_webhook_table(self, method, res):
|
||||
self.append("webhooks", {
|
||||
"webhook_id": res['webhook']['id'],
|
||||
"method": method
|
||||
})
|
||||
|
||||
def get_shopify_url(path, settings):
|
||||
if settings.app_type == "Private":
|
||||
return 'https://{}:{}@{}/{}'.format(settings.api_key, settings.get_password('password'), settings.shopify_url, path)
|
||||
else:
|
||||
return 'https://{}/{}'.format(settings.shopify_url, path)
|
||||
|
||||
def get_header(settings):
|
||||
header = {'Content-Type': 'application/json'}
|
||||
|
||||
return header
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_series():
|
||||
return {
|
||||
"sales_order_series" : frappe.get_meta("Sales Order").get_options("naming_series") or "SO-Shopify-",
|
||||
"sales_invoice_series" : frappe.get_meta("Sales Invoice").get_options("naming_series") or "SI-Shopify-",
|
||||
"delivery_note_series" : frappe.get_meta("Delivery Note").get_options("naming_series") or "DN-Shopify-"
|
||||
}
|
||||
|
||||
def setup_custom_fields():
|
||||
custom_fields = {
|
||||
"Customer": [
|
||||
dict(fieldname='shopify_customer_id', label='Shopify Customer Id',
|
||||
fieldtype='Data', insert_after='series', read_only=1, print_hide=1)
|
||||
],
|
||||
"Supplier": [
|
||||
dict(fieldname='shopify_supplier_id', label='Shopify Supplier Id',
|
||||
fieldtype='Data', insert_after='supplier_name', read_only=1, print_hide=1)
|
||||
],
|
||||
"Address": [
|
||||
dict(fieldname='shopify_address_id', label='Shopify Address Id',
|
||||
fieldtype='Data', insert_after='fax', read_only=1, print_hide=1)
|
||||
],
|
||||
"Item": [
|
||||
dict(fieldname='shopify_variant_id', label='Shopify Variant Id',
|
||||
fieldtype='Data', insert_after='item_code', read_only=1, print_hide=1),
|
||||
dict(fieldname='shopify_product_id', label='Shopify Product Id',
|
||||
fieldtype='Data', insert_after='item_code', read_only=1, print_hide=1),
|
||||
dict(fieldname='shopify_description', label='Shopify Description',
|
||||
fieldtype='Text Editor', insert_after='description', read_only=1, print_hide=1)
|
||||
],
|
||||
"Sales Order": [
|
||||
dict(fieldname='shopify_order_id', label='Shopify Order Id',
|
||||
fieldtype='Data', insert_after='title', read_only=1, print_hide=1),
|
||||
dict(fieldname='shopify_order_number', label='Shopify Order Number',
|
||||
fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1)
|
||||
],
|
||||
"Delivery Note":[
|
||||
dict(fieldname='shopify_order_id', label='Shopify Order Id',
|
||||
fieldtype='Data', insert_after='title', read_only=1, print_hide=1),
|
||||
dict(fieldname='shopify_order_number', label='Shopify Order Number',
|
||||
fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1),
|
||||
dict(fieldname='shopify_fulfillment_id', label='Shopify Fulfillment Id',
|
||||
fieldtype='Data', insert_after='title', read_only=1, print_hide=1)
|
||||
],
|
||||
"Sales Invoice": [
|
||||
dict(fieldname='shopify_order_id', label='Shopify Order Id',
|
||||
fieldtype='Data', insert_after='title', read_only=1, print_hide=1),
|
||||
dict(fieldname='shopify_order_number', label='Shopify Order Number',
|
||||
fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1)
|
||||
]
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields)
|
@ -1,71 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
def create_customer(shopify_customer, shopify_settings):
|
||||
import frappe.utils.nestedset
|
||||
|
||||
cust_name = (shopify_customer.get("first_name") + " " + (shopify_customer.get("last_name") \
|
||||
and shopify_customer.get("last_name") or "")) if shopify_customer.get("first_name")\
|
||||
else shopify_customer.get("email")
|
||||
|
||||
try:
|
||||
customer = frappe.get_doc({
|
||||
"doctype": "Customer",
|
||||
"name": shopify_customer.get("id"),
|
||||
"customer_name" : cust_name,
|
||||
"shopify_customer_id": shopify_customer.get("id"),
|
||||
"sync_with_shopify": 1,
|
||||
"customer_group": shopify_settings.customer_group,
|
||||
"territory": frappe.utils.nestedset.get_root_of("Territory"),
|
||||
"customer_type": _("Individual")
|
||||
})
|
||||
customer.flags.ignore_mandatory = True
|
||||
customer.insert(ignore_permissions=True)
|
||||
|
||||
if customer:
|
||||
create_customer_address(customer, shopify_customer)
|
||||
|
||||
frappe.db.commit()
|
||||
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def create_customer_address(customer, shopify_customer):
|
||||
addresses = shopify_customer.get("addresses", [])
|
||||
|
||||
if not addresses and "default_address" in shopify_customer:
|
||||
addresses.append(shopify_customer["default_address"])
|
||||
|
||||
for i, address in enumerate(addresses):
|
||||
address_title, address_type = get_address_title_and_type(customer.customer_name, i)
|
||||
try :
|
||||
frappe.get_doc({
|
||||
"doctype": "Address",
|
||||
"shopify_address_id": address.get("id"),
|
||||
"address_title": address_title,
|
||||
"address_type": address_type,
|
||||
"address_line1": address.get("address1") or "Address 1",
|
||||
"address_line2": address.get("address2"),
|
||||
"city": address.get("city") or "City",
|
||||
"state": address.get("province"),
|
||||
"pincode": address.get("zip"),
|
||||
"country": address.get("country"),
|
||||
"phone": address.get("phone"),
|
||||
"email_id": shopify_customer.get("email"),
|
||||
"links": [{
|
||||
"link_doctype": "Customer",
|
||||
"link_name": customer.name
|
||||
}]
|
||||
}).insert(ignore_mandatory=True)
|
||||
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def get_address_title_and_type(customer_name, index):
|
||||
address_type = _("Billing")
|
||||
address_title = customer_name
|
||||
if frappe.db.get_value("Address", "{0}-{1}".format(customer_name.strip(), address_type)):
|
||||
address_title = "{0}-{1}".format(customer_name.strip(), index)
|
||||
|
||||
return address_title, address_type
|
@ -1,309 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from erpnext import get_default_company
|
||||
from frappe.utils import cstr, cint, get_request_session
|
||||
from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import get_shopify_url, get_header
|
||||
|
||||
shopify_variants_attr_list = ["option1", "option2", "option3"]
|
||||
|
||||
def sync_item_from_shopify(shopify_settings, item):
|
||||
url = get_shopify_url("admin/api/2021-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
|
||||
session = get_request_session()
|
||||
|
||||
try:
|
||||
res = session.get(url, headers=get_header(shopify_settings))
|
||||
res.raise_for_status()
|
||||
|
||||
shopify_item = res.json()["product"]
|
||||
make_item(shopify_settings.warehouse, shopify_item)
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def make_item(warehouse, shopify_item):
|
||||
add_item_weight(shopify_item)
|
||||
|
||||
if has_variants(shopify_item):
|
||||
attributes = create_attribute(shopify_item)
|
||||
create_item(shopify_item, warehouse, 1, attributes)
|
||||
create_item_variants(shopify_item, warehouse, attributes, shopify_variants_attr_list)
|
||||
|
||||
else:
|
||||
shopify_item["variant_id"] = shopify_item['variants'][0]["id"]
|
||||
create_item(shopify_item, warehouse)
|
||||
|
||||
def add_item_weight(shopify_item):
|
||||
shopify_item["weight"] = shopify_item['variants'][0]["weight"]
|
||||
shopify_item["weight_unit"] = shopify_item['variants'][0]["weight_unit"]
|
||||
|
||||
def has_variants(shopify_item):
|
||||
if len(shopify_item.get("options")) >= 1 and "Default Title" not in shopify_item.get("options")[0]["values"]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def create_attribute(shopify_item):
|
||||
attribute = []
|
||||
# shopify item dict
|
||||
for attr in shopify_item.get('options'):
|
||||
if not frappe.db.get_value("Item Attribute", attr.get("name"), "name"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Item Attribute",
|
||||
"attribute_name": attr.get("name"),
|
||||
"item_attribute_values": [
|
||||
{
|
||||
"attribute_value": attr_value,
|
||||
"abbr":attr_value
|
||||
}
|
||||
for attr_value in attr.get("values")
|
||||
]
|
||||
}).insert()
|
||||
attribute.append({"attribute": attr.get("name")})
|
||||
|
||||
else:
|
||||
# check for attribute values
|
||||
item_attr = frappe.get_doc("Item Attribute", attr.get("name"))
|
||||
if not item_attr.numeric_values:
|
||||
set_new_attribute_values(item_attr, attr.get("values"))
|
||||
item_attr.save()
|
||||
attribute.append({"attribute": attr.get("name")})
|
||||
|
||||
else:
|
||||
attribute.append({
|
||||
"attribute": attr.get("name"),
|
||||
"from_range": item_attr.get("from_range"),
|
||||
"to_range": item_attr.get("to_range"),
|
||||
"increment": item_attr.get("increment"),
|
||||
"numeric_values": item_attr.get("numeric_values")
|
||||
})
|
||||
|
||||
return attribute
|
||||
|
||||
def set_new_attribute_values(item_attr, values):
|
||||
for attr_value in values:
|
||||
if not any((d.abbr.lower() == attr_value.lower() or d.attribute_value.lower() == attr_value.lower())\
|
||||
for d in item_attr.item_attribute_values):
|
||||
item_attr.append("item_attribute_values", {
|
||||
"attribute_value": attr_value,
|
||||
"abbr": attr_value
|
||||
})
|
||||
|
||||
def create_item(shopify_item, warehouse, has_variant=0, attributes=None,variant_of=None):
|
||||
item_dict = {
|
||||
"doctype": "Item",
|
||||
"shopify_product_id": shopify_item.get("id"),
|
||||
"shopify_variant_id": shopify_item.get("variant_id"),
|
||||
"variant_of": variant_of,
|
||||
"sync_with_shopify": 1,
|
||||
"is_stock_item": 1,
|
||||
"item_code": cstr(shopify_item.get("item_code")) or cstr(shopify_item.get("id")),
|
||||
"item_name": shopify_item.get("title", '').strip(),
|
||||
"description": shopify_item.get("body_html") or shopify_item.get("title"),
|
||||
"shopify_description": shopify_item.get("body_html") or shopify_item.get("title"),
|
||||
"item_group": get_item_group(shopify_item.get("product_type")),
|
||||
"has_variants": has_variant,
|
||||
"attributes":attributes or [],
|
||||
"stock_uom": shopify_item.get("uom") or _("Nos"),
|
||||
"stock_keeping_unit": shopify_item.get("sku") or get_sku(shopify_item),
|
||||
"default_warehouse": warehouse,
|
||||
"image": get_item_image(shopify_item),
|
||||
"weight_uom": shopify_item.get("weight_unit"),
|
||||
"weight_per_unit": shopify_item.get("weight"),
|
||||
"default_supplier": get_supplier(shopify_item),
|
||||
"item_defaults": [
|
||||
{
|
||||
"company": get_default_company()
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
if not is_item_exists(item_dict, attributes, variant_of=variant_of):
|
||||
item_details = get_item_details(shopify_item)
|
||||
name = ''
|
||||
|
||||
if not item_details:
|
||||
new_item = frappe.get_doc(item_dict)
|
||||
new_item.insert(ignore_permissions=True, ignore_mandatory=True)
|
||||
name = new_item.name
|
||||
|
||||
if not name:
|
||||
name = item_details.name
|
||||
|
||||
if not has_variant:
|
||||
add_to_price_list(shopify_item, name)
|
||||
|
||||
frappe.db.commit()
|
||||
|
||||
def create_item_variants(shopify_item, warehouse, attributes, shopify_variants_attr_list):
|
||||
template_item = frappe.db.get_value("Item", filters={"shopify_product_id": shopify_item.get("id")},
|
||||
fieldname=["name", "stock_uom"], as_dict=True)
|
||||
|
||||
if template_item:
|
||||
for variant in shopify_item.get("variants"):
|
||||
shopify_item_variant = {
|
||||
"id" : variant.get("id"),
|
||||
"item_code": variant.get("id"),
|
||||
"title": variant.get("title"),
|
||||
"product_type": shopify_item.get("product_type"),
|
||||
"sku": variant.get("sku"),
|
||||
"uom": template_item.stock_uom or _("Nos"),
|
||||
"item_price": variant.get("price"),
|
||||
"variant_id": variant.get("id"),
|
||||
"weight_unit": variant.get("weight_unit"),
|
||||
"weight": variant.get("weight")
|
||||
}
|
||||
|
||||
for i, variant_attr in enumerate(shopify_variants_attr_list):
|
||||
if variant.get(variant_attr):
|
||||
attributes[i].update({"attribute_value": get_attribute_value(variant.get(variant_attr), attributes[i])})
|
||||
create_item(shopify_item_variant, warehouse, 0, attributes, template_item.name)
|
||||
|
||||
def get_attribute_value(variant_attr_val, attribute):
|
||||
attribute_value = frappe.db.sql("""select attribute_value from `tabItem Attribute Value`
|
||||
where parent = %s and (abbr = %s or attribute_value = %s)""", (attribute["attribute"], variant_attr_val,
|
||||
variant_attr_val), as_list=1)
|
||||
return attribute_value[0][0] if len(attribute_value)>0 else cint(variant_attr_val)
|
||||
|
||||
def get_item_group(product_type=None):
|
||||
import frappe.utils.nestedset
|
||||
parent_item_group = frappe.utils.nestedset.get_root_of("Item Group")
|
||||
|
||||
if product_type:
|
||||
if not frappe.db.get_value("Item Group", product_type, "name"):
|
||||
item_group = frappe.get_doc({
|
||||
"doctype": "Item Group",
|
||||
"item_group_name": product_type,
|
||||
"parent_item_group": parent_item_group,
|
||||
"is_group": "No"
|
||||
}).insert()
|
||||
return item_group.name
|
||||
else:
|
||||
return product_type
|
||||
else:
|
||||
return parent_item_group
|
||||
|
||||
|
||||
def get_sku(item):
|
||||
if item.get("variants"):
|
||||
return item.get("variants")[0].get("sku")
|
||||
return ""
|
||||
|
||||
def add_to_price_list(item, name):
|
||||
shopify_settings = frappe.db.get_value("Shopify Settings", None, ["price_list", "update_price_in_erpnext_price_list"], as_dict=1)
|
||||
if not shopify_settings.update_price_in_erpnext_price_list:
|
||||
return
|
||||
|
||||
item_price_name = frappe.db.get_value("Item Price",
|
||||
{"item_code": name, "price_list": shopify_settings.price_list}, "name")
|
||||
|
||||
if not item_price_name:
|
||||
frappe.get_doc({
|
||||
"doctype": "Item Price",
|
||||
"price_list": shopify_settings.price_list,
|
||||
"item_code": name,
|
||||
"price_list_rate": item.get("item_price") or item.get("variants")[0].get("price")
|
||||
}).insert()
|
||||
else:
|
||||
item_rate = frappe.get_doc("Item Price", item_price_name)
|
||||
item_rate.price_list_rate = item.get("item_price") or item.get("variants")[0].get("price")
|
||||
item_rate.save()
|
||||
|
||||
def get_item_image(shopify_item):
|
||||
if shopify_item.get("image"):
|
||||
return shopify_item.get("image").get("src")
|
||||
return None
|
||||
|
||||
def get_supplier(shopify_item):
|
||||
if shopify_item.get("vendor"):
|
||||
supplier = frappe.db.sql("""select name from tabSupplier
|
||||
where name = %s or shopify_supplier_id = %s """, (shopify_item.get("vendor"),
|
||||
shopify_item.get("vendor").lower()), as_list=1)
|
||||
|
||||
if not supplier:
|
||||
supplier = frappe.get_doc({
|
||||
"doctype": "Supplier",
|
||||
"supplier_name": shopify_item.get("vendor"),
|
||||
"shopify_supplier_id": shopify_item.get("vendor").lower(),
|
||||
"supplier_group": get_supplier_group()
|
||||
}).insert()
|
||||
return supplier.name
|
||||
else:
|
||||
return shopify_item.get("vendor")
|
||||
else:
|
||||
return ""
|
||||
|
||||
def get_supplier_group():
|
||||
supplier_group = frappe.db.get_value("Supplier Group", _("Shopify Supplier"))
|
||||
if not supplier_group:
|
||||
supplier_group = frappe.get_doc({
|
||||
"doctype": "Supplier Group",
|
||||
"supplier_group_name": _("Shopify Supplier")
|
||||
}).insert()
|
||||
return supplier_group.name
|
||||
return supplier_group
|
||||
|
||||
def get_item_details(shopify_item):
|
||||
item_details = {}
|
||||
|
||||
item_details = frappe.db.get_value("Item", {"shopify_product_id": shopify_item.get("id")},
|
||||
["name", "stock_uom", "item_name"], as_dict=1)
|
||||
|
||||
if item_details:
|
||||
return item_details
|
||||
|
||||
else:
|
||||
item_details = frappe.db.get_value("Item", {"shopify_variant_id": shopify_item.get("id")},
|
||||
["name", "stock_uom", "item_name"], as_dict=1)
|
||||
return item_details
|
||||
|
||||
def is_item_exists(shopify_item, attributes=None, variant_of=None):
|
||||
if variant_of:
|
||||
name = variant_of
|
||||
else:
|
||||
name = frappe.db.get_value("Item", {"item_name": shopify_item.get("item_name")})
|
||||
|
||||
if name:
|
||||
item = frappe.get_doc("Item", name)
|
||||
item.flags.ignore_mandatory=True
|
||||
|
||||
if not variant_of and not item.shopify_product_id:
|
||||
item.shopify_product_id = shopify_item.get("shopify_product_id")
|
||||
item.shopify_variant_id = shopify_item.get("shopify_variant_id")
|
||||
item.save()
|
||||
return True
|
||||
|
||||
if item.shopify_product_id and attributes and attributes[0].get("attribute_value"):
|
||||
if not variant_of:
|
||||
variant_of = frappe.db.get_value("Item",
|
||||
{"shopify_product_id": item.shopify_product_id}, "variant_of")
|
||||
|
||||
# create conditions for all item attributes,
|
||||
# as we are putting condition basis on OR it will fetch all items matching either of conditions
|
||||
# thus comparing matching conditions with len(attributes)
|
||||
# which will give exact matching variant item.
|
||||
|
||||
conditions = ["(iv.attribute='{0}' and iv.attribute_value = '{1}')"\
|
||||
.format(attr.get("attribute"), attr.get("attribute_value")) for attr in attributes]
|
||||
|
||||
conditions = "( {0} ) and iv.parent = it.name ) = {1}".format(" or ".join(conditions), len(attributes))
|
||||
|
||||
parent = frappe.db.sql(""" select * from tabItem it where
|
||||
( select count(*) from `tabItem Variant Attribute` iv
|
||||
where {conditions} and it.variant_of = %s """.format(conditions=conditions) ,
|
||||
variant_of, as_list=1)
|
||||
|
||||
if parent:
|
||||
variant = frappe.get_doc("Item", parent[0][0])
|
||||
variant.flags.ignore_mandatory = True
|
||||
|
||||
variant.shopify_product_id = shopify_item.get("shopify_product_id")
|
||||
variant.shopify_variant_id = shopify_item.get("shopify_variant_id")
|
||||
variant.save()
|
||||
return False
|
||||
|
||||
if item.shopify_product_id and item.shopify_product_id != shopify_item.get("shopify_product_id"):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
else:
|
||||
return False
|
@ -1,527 +0,0 @@
|
||||
[
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Print Settings",
|
||||
"fieldname": "compact_item_print",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"insert_after": "with_letterhead",
|
||||
"label": "Compact Item Print",
|
||||
"modified": "2016-06-06 15:18:17.025602",
|
||||
"name": "Print Settings-compact_item_print",
|
||||
"no_copy": 0,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Customer",
|
||||
"fieldname": "shopify_customer_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"insert_after": "naming_series",
|
||||
"label": "Shopify Customer Id",
|
||||
"modified": "2016-01-15 17:25:28.991818",
|
||||
"name": "Customer-shopify_customer_id",
|
||||
"no_copy": 1,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Address",
|
||||
"fieldname": "shopify_address_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"insert_after": "fax",
|
||||
"label": "Shopify Address Id",
|
||||
"modified": "2016-01-15 17:50:52.213743",
|
||||
"name": "Address-shopify_address_id",
|
||||
"no_copy": 1,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Sales Order",
|
||||
"fieldname": "shopify_order_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"insert_after": "title",
|
||||
"label": "Shopify Order Id",
|
||||
"modified": "2016-01-18 09:55:50.764524",
|
||||
"name": "Sales Order-shopify_order_id",
|
||||
"no_copy": 1,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Item",
|
||||
"fieldname": "shopify_product_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"insert_after": "item_code",
|
||||
"label": "Shopify Product Id",
|
||||
"modified": "2016-01-19 15:44:16.132952",
|
||||
"name": "Item-shopify_product_id",
|
||||
"no_copy": 1,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Sales Invoice",
|
||||
"fieldname": "shopify_order_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"insert_after": "naming_series",
|
||||
"label": "Shopify Order Id",
|
||||
"modified": "2016-01-19 16:30:12.261797",
|
||||
"name": "Sales Invoice-shopify_order_id",
|
||||
"no_copy": 1,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Delivery Note",
|
||||
"fieldname": "shopify_order_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"insert_after": "title",
|
||||
"label": "Shopify Order Id",
|
||||
"modified": "2016-01-19 16:30:31.201198",
|
||||
"name": "Delivery Note-shopify_order_id",
|
||||
"no_copy": 1,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Item",
|
||||
"fieldname": "stock_keeping_unit",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"insert_after": "stock_uom",
|
||||
"label": "Stock Keeping Unit",
|
||||
"modified": "2015-11-10 09:29:10.854943",
|
||||
"name": "Item-stock_keeping_unit",
|
||||
"no_copy": 1,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"default": "0",
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Item",
|
||||
"fieldname": "sync_with_shopify",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"insert_after": "is_stock_item",
|
||||
"label": "Sync With Shopify",
|
||||
"modified": "2015-10-12 15:54:31.997714",
|
||||
"name": "Item-sync_with_shopify",
|
||||
"no_copy": 0,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Customer",
|
||||
"fieldname": "sync_with_shopify",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"insert_after": "is_frozen",
|
||||
"label": "Sync With Shopify",
|
||||
"modified": "2015-10-01 17:31:55.758826",
|
||||
"name": "Customer-sync_with_shopify",
|
||||
"no_copy": 0,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Item",
|
||||
"fieldname": "shopify_variant_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"insert_after": "item_code",
|
||||
"label": "Variant Id",
|
||||
"modified": "2015-11-09 18:26:50.825858",
|
||||
"name": "Item-shopify_variant_id",
|
||||
"no_copy": 1,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Item",
|
||||
"fieldname": "sync_qty_with_shopify",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"insert_after": "item_code",
|
||||
"label": "Sync Quantity With Shopify",
|
||||
"modified": "2015-12-29 08:37:46.183295",
|
||||
"name": "Item-sync_qty_with_shopify",
|
||||
"no_copy": 0,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Delivery Note",
|
||||
"fieldname": "shopify_fulfillment_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"insert_after": "title",
|
||||
"label": "Shopify Fulfillment Id",
|
||||
"modified": "2016-01-20 23:50:35.609543",
|
||||
"name": "Delivery Note-shopify_fulfillment_id",
|
||||
"no_copy": 1,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Supplier",
|
||||
"fieldname": "shopify_supplier_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"insert_after": "supplier_name",
|
||||
"label": "Shopify Supplier Id",
|
||||
"modified": "2016-02-01 15:41:25.818306",
|
||||
"name": "Supplier-shopify_supplier_id",
|
||||
"no_copy": 1,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Item",
|
||||
"fieldname": "shopify_description",
|
||||
"fieldtype": "Text Editor",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"insert_after": "section_break_11",
|
||||
"label": "shopify_description",
|
||||
"modified": "2016-06-15 12:15:36.325581",
|
||||
"name": "Item-shopify_description",
|
||||
"no_copy": 0,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"report_hide": 1,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
}
|
||||
]
|
@ -1,59 +0,0 @@
|
||||
{
|
||||
"customer": {
|
||||
"id": 2324518599,
|
||||
"email": "andrew@wyatt.co.in",
|
||||
"accepts_marketing": false,
|
||||
"created_at": "2016-01-20T17:18:35+05:30",
|
||||
"updated_at": "2016-01-20T17:22:23+05:30",
|
||||
"first_name": "Andrew",
|
||||
"last_name": "Wyatt",
|
||||
"orders_count": 0,
|
||||
"state": "disabled",
|
||||
"total_spent": "0.00",
|
||||
"last_order_id": null,
|
||||
"note": "",
|
||||
"verified_email": true,
|
||||
"multipass_identifier": null,
|
||||
"tax_exempt": false,
|
||||
"tags": "",
|
||||
"last_order_name": null,
|
||||
"default_address": {
|
||||
"id": 2476804295,
|
||||
"first_name": "Andrew",
|
||||
"last_name": "Wyatt",
|
||||
"company": "Wyatt Inc.",
|
||||
"address1": "B-11, Betahouse",
|
||||
"address2": "Street 11, Sector 52",
|
||||
"city": "Manhattan",
|
||||
"province": "New York",
|
||||
"country": "United States",
|
||||
"zip": "10027",
|
||||
"phone": "145-112211",
|
||||
"name": "Andrew Wyatt",
|
||||
"province_code": "NY",
|
||||
"country_code": "US",
|
||||
"country_name": "United States",
|
||||
"default": true
|
||||
},
|
||||
"addresses": [
|
||||
{
|
||||
"id": 2476804295,
|
||||
"first_name": "Andrew",
|
||||
"last_name": "Wyatt",
|
||||
"company": "Wyatt Inc.",
|
||||
"address1": "B-11, Betahouse",
|
||||
"address2": "Street 11, Sector 52",
|
||||
"city": "Manhattan",
|
||||
"province": "New York",
|
||||
"country": "United States",
|
||||
"zip": "10027",
|
||||
"phone": "145-112211",
|
||||
"name": "Andrew Wyatt",
|
||||
"province_code": "NY",
|
||||
"country_code": "US",
|
||||
"country_name": "United States",
|
||||
"default": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
{
|
||||
"product": {
|
||||
"id": 4059739520,
|
||||
"title": "Shopify Test Item",
|
||||
"body_html": "<div>Hold back Spin Medallion-Set of 2</div>\n<div></div>\n<div>Finish: Plated/ Powder Coated</div>\n<div>Material: Iron</div>\n<div>Color Finish: Satin Silver, Brown Oil Rubbed, Roman Bronze</div>\n<div>Qty: 1 Set</div>",
|
||||
"vendor": "Boa casa",
|
||||
"product_type": "Curtain Accessories",
|
||||
"created_at": "2016-01-18T17:16:37+05:30",
|
||||
"handle": "1001624-01",
|
||||
"updated_at": "2016-01-20T17:26:44+05:30",
|
||||
"published_at": "2016-01-18T17:16:37+05:30",
|
||||
"template_suffix": null,
|
||||
"published_scope": "global",
|
||||
"tags": "Category_Curtain Accessories, Type_Holdback",
|
||||
"variants": [{
|
||||
"id": 13917612359,
|
||||
"product_id": 4059739520,
|
||||
"title": "Test BALCK Item",
|
||||
"price": "499.00",
|
||||
"sku": "",
|
||||
"position": 1,
|
||||
"grams": 0,
|
||||
"inventory_policy": "continue",
|
||||
"compare_at_price": null,
|
||||
"fulfillment_service": "manual",
|
||||
"inventory_management": "shopify",
|
||||
"option1": "BLACK",
|
||||
"option2": null,
|
||||
"option3": null,
|
||||
"created_at": "2016-01-18T17:16:37+05:30",
|
||||
"updated_at": "2016-01-20T17:26:44+05:30",
|
||||
"requires_shipping": true,
|
||||
"taxable": true,
|
||||
"barcode": "",
|
||||
"inventory_quantity": -1,
|
||||
"old_inventory_quantity": -1,
|
||||
"image_id": 8539321735,
|
||||
"weight": 0,
|
||||
"weight_unit": "kg"
|
||||
}, {
|
||||
"id": 13917612423,
|
||||
"product_id": 4059739520,
|
||||
"title": "Test BLUE Item",
|
||||
"price": "499.00",
|
||||
"sku": "",
|
||||
"position": 2,
|
||||
"grams": 0,
|
||||
"inventory_policy": "continue",
|
||||
"compare_at_price": null,
|
||||
"fulfillment_service": "manual",
|
||||
"inventory_management": "shopify",
|
||||
"option1": "BLUE",
|
||||
"option2": null,
|
||||
"option3": null,
|
||||
"created_at": "2016-01-18T17:16:37+05:30",
|
||||
"updated_at": "2016-01-20T17:26:44+05:30",
|
||||
"requires_shipping": true,
|
||||
"taxable": true,
|
||||
"barcode": "",
|
||||
"inventory_quantity": -1,
|
||||
"old_inventory_quantity": -1,
|
||||
"image_id": null,
|
||||
"weight": 0,
|
||||
"weight_unit": "kg"
|
||||
}, {
|
||||
"id": 13917612487,
|
||||
"product_id": 4059739520,
|
||||
"title": "Test White Item",
|
||||
"price": "499.00",
|
||||
"sku": "",
|
||||
"position": 3,
|
||||
"grams": 0,
|
||||
"inventory_policy": "continue",
|
||||
"compare_at_price": null,
|
||||
"fulfillment_service": "manual",
|
||||
"inventory_management": "shopify",
|
||||
"option1": "White",
|
||||
"option2": null,
|
||||
"option3": null,
|
||||
"created_at": "2016-01-18T17:16:37+05:30",
|
||||
"updated_at": "2016-01-18T17:16:37+05:30",
|
||||
"requires_shipping": true,
|
||||
"taxable": true,
|
||||
"barcode": "",
|
||||
"inventory_quantity": 0,
|
||||
"old_inventory_quantity": 0,
|
||||
"image_id": null,
|
||||
"weight": 0,
|
||||
"weight_unit": "kg"
|
||||
}],
|
||||
"options": [{
|
||||
"id": 4985027399,
|
||||
"product_id": 4059739520,
|
||||
"name": "Colour",
|
||||
"position": 1,
|
||||
"values": [
|
||||
"BLACK",
|
||||
"BLUE",
|
||||
"White"
|
||||
]
|
||||
}],
|
||||
"images": [{
|
||||
"id": 8539321735,
|
||||
"product_id": 4059739520,
|
||||
"position": 1,
|
||||
"created_at": "2016-01-18T17:16:37+05:30",
|
||||
"updated_at": "2016-01-18T17:16:37+05:30",
|
||||
"src": "https://cdn.shopify.com/s/files/1/1123/0654/products/2015-12-17_6.png?v=1453117597",
|
||||
"variant_ids": [
|
||||
13917612359
|
||||
]
|
||||
}],
|
||||
"image": {
|
||||
"id": 8539321735,
|
||||
"product_id": 4059739520,
|
||||
"position": 1,
|
||||
"created_at": "2016-01-18T17:16:37+05:30",
|
||||
"updated_at": "2016-01-18T17:16:37+05:30",
|
||||
"src": "https://cdn.shopify.com/s/files/1/1123/0654/products/2015-12-17_6.png?v=1453117597",
|
||||
"variant_ids": [
|
||||
13917612359
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -1,270 +0,0 @@
|
||||
{
|
||||
"order": {
|
||||
"id": 2414345735,
|
||||
"email": "andrew@wyatt.co.in",
|
||||
"closed_at": null,
|
||||
"created_at": "2016-01-20T17:26:39+05:30",
|
||||
"updated_at": "2016-01-20T17:27:15+05:30",
|
||||
"number": 5,
|
||||
"note": "",
|
||||
"token": "660fed25987517b733644a8c9ec7c8e0",
|
||||
"gateway": "manual",
|
||||
"test": false,
|
||||
"total_price": "1018.00",
|
||||
"subtotal_price": "998.00",
|
||||
"total_weight": 0,
|
||||
"total_tax": "0.00",
|
||||
"taxes_included": false,
|
||||
"currency": "INR",
|
||||
"financial_status": "paid",
|
||||
"confirmed": true,
|
||||
"total_discounts": "0.00",
|
||||
"total_line_items_price": "998.00",
|
||||
"cart_token": null,
|
||||
"buyer_accepts_marketing": false,
|
||||
"name": "#1005",
|
||||
"referring_site": null,
|
||||
"landing_site": null,
|
||||
"cancelled_at": null,
|
||||
"cancel_reason": null,
|
||||
"total_price_usd": "15.02",
|
||||
"checkout_token": null,
|
||||
"reference": null,
|
||||
"user_id": 55391175,
|
||||
"location_id": null,
|
||||
"source_identifier": null,
|
||||
"source_url": null,
|
||||
"processed_at": "2016-01-20T17:26:39+05:30",
|
||||
"device_id": null,
|
||||
"browser_ip": null,
|
||||
"landing_site_ref": null,
|
||||
"order_number": 1005,
|
||||
"discount_codes": [],
|
||||
"note_attributes": [],
|
||||
"payment_gateway_names": [
|
||||
"manual"
|
||||
],
|
||||
"processing_method": "manual",
|
||||
"checkout_id": null,
|
||||
"source_name": "shopify_draft_order",
|
||||
"fulfillment_status": "fulfilled",
|
||||
"tax_lines": [],
|
||||
"tags": "",
|
||||
"contact_email": "andrew@wyatt.co.in",
|
||||
"line_items": [
|
||||
{
|
||||
"id": 4125768135,
|
||||
"variant_id": 13917612359,
|
||||
"title": "Shopify Test Item",
|
||||
"quantity": 1,
|
||||
"price": "499.00",
|
||||
"grams": 0,
|
||||
"sku": "",
|
||||
"variant_title": "Roman BALCK 1",
|
||||
"vendor": "Boa casa",
|
||||
"fulfillment_service": "manual",
|
||||
"product_id": 4059739527,
|
||||
"requires_shipping": true,
|
||||
"taxable": true,
|
||||
"gift_card": false,
|
||||
"name": "Roman BALCK 1",
|
||||
"variant_inventory_management": "shopify",
|
||||
"properties": [],
|
||||
"product_exists": true,
|
||||
"fulfillable_quantity": 0,
|
||||
"total_discount": "0.00",
|
||||
"fulfillment_status": "fulfilled",
|
||||
"tax_lines": []
|
||||
},
|
||||
{
|
||||
"id": 4125768199,
|
||||
"variant_id": 13917612423,
|
||||
"title": "Shopify Test Item",
|
||||
"quantity": 1,
|
||||
"price": "499.00",
|
||||
"grams": 0,
|
||||
"sku": "",
|
||||
"variant_title": "Satin BLUE 1",
|
||||
"vendor": "Boa casa",
|
||||
"fulfillment_service": "manual",
|
||||
"product_id": 4059739527,
|
||||
"requires_shipping": true,
|
||||
"taxable": true,
|
||||
"gift_card": false,
|
||||
"name": "Satin BLUE 1",
|
||||
"variant_inventory_management": "shopify",
|
||||
"properties": [],
|
||||
"product_exists": true,
|
||||
"fulfillable_quantity": 0,
|
||||
"total_discount": "0.00",
|
||||
"fulfillment_status": "fulfilled",
|
||||
"tax_lines": []
|
||||
}
|
||||
],
|
||||
"shipping_lines": [
|
||||
{
|
||||
"id": 2108906247,
|
||||
"title": "International Shipping",
|
||||
"price": "20.00",
|
||||
"code": "International Shipping",
|
||||
"source": "shopify",
|
||||
"phone": null,
|
||||
"tax_lines": []
|
||||
}
|
||||
],
|
||||
"billing_address": {
|
||||
"first_name": "Andrew",
|
||||
"address1": "B-11, Betahouse",
|
||||
"phone": "145-112211",
|
||||
"city": "Manhattan",
|
||||
"zip": "10027",
|
||||
"province": "New York",
|
||||
"country": "United States",
|
||||
"last_name": "Wyatt",
|
||||
"address2": "Street 11, Sector 52",
|
||||
"company": "Wyatt Inc.",
|
||||
"latitude": 40.8138912,
|
||||
"longitude": -73.96243270000001,
|
||||
"name": "Andrew Wyatt",
|
||||
"country_code": "US",
|
||||
"province_code": "NY"
|
||||
},
|
||||
"shipping_address": {
|
||||
"first_name": "Andrew",
|
||||
"address1": "B-11, Betahouse",
|
||||
"phone": "145-112211",
|
||||
"city": "Manhattan",
|
||||
"zip": "10027",
|
||||
"province": "New York",
|
||||
"country": "United States",
|
||||
"last_name": "Wyatt",
|
||||
"address2": "Street 11, Sector 52",
|
||||
"company": "Wyatt Inc.",
|
||||
"latitude": 40.8138912,
|
||||
"longitude": -73.96243270000001,
|
||||
"name": "Andrew Wyatt",
|
||||
"country_code": "US",
|
||||
"province_code": "NY"
|
||||
},
|
||||
"fulfillments": [
|
||||
{
|
||||
"id": 1849629255,
|
||||
"order_id": 2414345735,
|
||||
"status": "success",
|
||||
"created_at": "2016-01-20T17:27:15+05:30",
|
||||
"service": "manual",
|
||||
"updated_at": "2016-01-20T17:27:15+05:30",
|
||||
"tracking_company": null,
|
||||
"tracking_number": null,
|
||||
"tracking_numbers": [],
|
||||
"tracking_url": null,
|
||||
"tracking_urls": [],
|
||||
"receipt": {},
|
||||
"line_items": [
|
||||
{
|
||||
"id": 4125768199,
|
||||
"variant_id": 13917612423,
|
||||
"title": "1001624/01",
|
||||
"quantity": 1,
|
||||
"price": "499.00",
|
||||
"grams": 0,
|
||||
"sku": "",
|
||||
"variant_title": "Satin Silver",
|
||||
"vendor": "Boa casa",
|
||||
"fulfillment_service": "manual",
|
||||
"product_id": 4059739527,
|
||||
"requires_shipping": true,
|
||||
"taxable": true,
|
||||
"gift_card": false,
|
||||
"name": "1001624/01 - Satin Silver",
|
||||
"variant_inventory_management": "shopify",
|
||||
"properties": [],
|
||||
"product_exists": true,
|
||||
"fulfillable_quantity": 0,
|
||||
"total_discount": "0.00",
|
||||
"fulfillment_status": "fulfilled",
|
||||
"tax_lines": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1849628167,
|
||||
"order_id": 2414345735,
|
||||
"status": "success",
|
||||
"created_at": "2016-01-20T17:26:58+05:30",
|
||||
"service": "manual",
|
||||
"updated_at": "2016-01-20T17:26:58+05:30",
|
||||
"tracking_company": null,
|
||||
"tracking_number": null,
|
||||
"tracking_numbers": [],
|
||||
"tracking_url": null,
|
||||
"tracking_urls": [],
|
||||
"receipt": {},
|
||||
"line_items": [
|
||||
{
|
||||
"id": 4125768135,
|
||||
"variant_id": 13917612359,
|
||||
"title": "1001624/01",
|
||||
"quantity": 1,
|
||||
"price": "499.00",
|
||||
"grams": 0,
|
||||
"sku": "",
|
||||
"variant_title": "Roman Bronze",
|
||||
"vendor": "Boa casa",
|
||||
"fulfillment_service": "manual",
|
||||
"product_id": 4059739527,
|
||||
"requires_shipping": true,
|
||||
"taxable": true,
|
||||
"gift_card": false,
|
||||
"name": "1001624/01 - Roman Bronze",
|
||||
"variant_inventory_management": "shopify",
|
||||
"properties": [],
|
||||
"product_exists": true,
|
||||
"fulfillable_quantity": 0,
|
||||
"total_discount": "0.00",
|
||||
"fulfillment_status": "fulfilled",
|
||||
"tax_lines": []
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"refunds": [],
|
||||
"customer": {
|
||||
"id": 2324518599,
|
||||
"email": "andrew@wyatt.co.in",
|
||||
"accepts_marketing": false,
|
||||
"created_at": "2016-01-20T17:18:35+05:30",
|
||||
"updated_at": "2016-01-20T17:26:39+05:30",
|
||||
"first_name": "Andrew",
|
||||
"last_name": "Wyatt",
|
||||
"orders_count": 1,
|
||||
"state": "disabled",
|
||||
"total_spent": "1018.00",
|
||||
"last_order_id": 2414345735,
|
||||
"note": "",
|
||||
"verified_email": true,
|
||||
"multipass_identifier": null,
|
||||
"tax_exempt": false,
|
||||
"tags": "",
|
||||
"last_order_name": "#1005",
|
||||
"default_address": {
|
||||
"id": 2476804295,
|
||||
"first_name": "Andrew",
|
||||
"last_name": "Wyatt",
|
||||
"company": "Wyatt Inc.",
|
||||
"address1": "B-11, Betahouse",
|
||||
"address2": "Street 11, Sector 52",
|
||||
"city": "Manhattan",
|
||||
"province": "New York",
|
||||
"country": "United States",
|
||||
"zip": "10027",
|
||||
"phone": "145-112211",
|
||||
"name": "Andrew Wyatt",
|
||||
"province_code": "NY",
|
||||
"country_code": "US",
|
||||
"country_name": "United States",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Shopify Settings", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Shopify Settings
|
||||
() => frappe.tests.make('Shopify Settings', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
@ -1,107 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
import unittest, os, json
|
||||
from frappe.utils import cstr, cint
|
||||
from erpnext.erpnext_integrations.connectors.shopify_connection import create_order
|
||||
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import make_item
|
||||
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer
|
||||
from frappe.core.doctype.data_import.data_import import import_doc
|
||||
|
||||
|
||||
class ShopifySettings(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
cls.allow_negative_stock = cint(frappe.db.get_value('Stock Settings', None, 'allow_negative_stock'))
|
||||
if not cls.allow_negative_stock:
|
||||
frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 1)
|
||||
|
||||
# use the fixture data
|
||||
import_doc(path=frappe.get_app_path("erpnext", "erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json"))
|
||||
|
||||
frappe.reload_doctype("Customer")
|
||||
frappe.reload_doctype("Sales Order")
|
||||
frappe.reload_doctype("Delivery Note")
|
||||
frappe.reload_doctype("Sales Invoice")
|
||||
|
||||
cls.setup_shopify()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
if not cls.allow_negative_stock:
|
||||
frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0)
|
||||
|
||||
@classmethod
|
||||
def setup_shopify(cls):
|
||||
shopify_settings = frappe.get_doc("Shopify Settings")
|
||||
shopify_settings.taxes = []
|
||||
|
||||
shopify_settings.update({
|
||||
"app_type": "Private",
|
||||
"shopify_url": "test.myshopify.com",
|
||||
"api_key": "17702c7c4452b9c5d235240b6e7a39da",
|
||||
"password": "17702c7c4452b9c5d235240b6e7a39da",
|
||||
"shared_secret": "17702c7c4452b9c5d235240b6e7a39da",
|
||||
"price_list": "_Test Price List",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"cash_bank_account": "Cash - _TC",
|
||||
"account": "Cash - _TC",
|
||||
"customer_group": "_Test Customer Group",
|
||||
"cost_center": "Main - _TC",
|
||||
"taxes": [
|
||||
{
|
||||
"shopify_tax": "International Shipping",
|
||||
"tax_account":"Legal Expenses - _TC"
|
||||
}
|
||||
],
|
||||
"enable_shopify": 0,
|
||||
"sales_order_series": "SO-",
|
||||
"sync_sales_invoice": 1,
|
||||
"sales_invoice_series": "SINV-",
|
||||
"sync_delivery_note": 1,
|
||||
"delivery_note_series": "DN-"
|
||||
}).save(ignore_permissions=True)
|
||||
|
||||
cls.shopify_settings = shopify_settings
|
||||
|
||||
def test_order(self):
|
||||
# Create Customer
|
||||
with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_customer.json")) as shopify_customer:
|
||||
shopify_customer = json.load(shopify_customer)
|
||||
create_customer(shopify_customer.get("customer"), self.shopify_settings)
|
||||
|
||||
# Create Item
|
||||
with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_item.json")) as shopify_item:
|
||||
shopify_item = json.load(shopify_item)
|
||||
make_item("_Test Warehouse - _TC", shopify_item.get("product"))
|
||||
|
||||
# Create Order
|
||||
with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_order.json")) as shopify_order:
|
||||
shopify_order = json.load(shopify_order)
|
||||
|
||||
create_order(shopify_order.get("order"), self.shopify_settings, False, company="_Test Company")
|
||||
|
||||
sales_order = frappe.get_doc("Sales Order", {"shopify_order_id": cstr(shopify_order.get("order").get("id"))})
|
||||
|
||||
self.assertEqual(cstr(shopify_order.get("order").get("id")), sales_order.shopify_order_id)
|
||||
|
||||
# Check for customer
|
||||
shopify_order_customer_id = cstr(shopify_order.get("order").get("customer").get("id"))
|
||||
sales_order_customer_id = frappe.get_value("Customer", sales_order.customer, "shopify_customer_id")
|
||||
|
||||
self.assertEqual(shopify_order_customer_id, sales_order_customer_id)
|
||||
|
||||
# Check sales invoice
|
||||
sales_invoice = frappe.get_doc("Sales Invoice", {"shopify_order_id": sales_order.shopify_order_id})
|
||||
self.assertEqual(sales_invoice.rounded_total, sales_order.rounded_total)
|
||||
|
||||
# Check delivery note
|
||||
delivery_note_count = frappe.db.sql("""select count(*) from `tabDelivery Note`
|
||||
where shopify_order_id = %s""", sales_order.shopify_order_id)[0][0]
|
||||
|
||||
self.assertEqual(delivery_note_count, len(shopify_order.get("order").get("fulfillments")))
|
@ -1,133 +0,0 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2015-10-05 16:55:20.455371",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 0,
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "shopify_tax",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Shopify Tax/Shipping Title",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "tax_account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "ERPNext Account",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-04-09 11:36:49.272815",
|
||||
"modified_by": "Administrator",
|
||||
"module": "ERPNext Integrations",
|
||||
"name": "Shopify Tax Account",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class ShopifyTaxAccount(Document):
|
||||
pass
|
@ -1,103 +0,0 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2018-04-10 17:06:22.697427",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "webhook_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Webhook ID",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "method",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Method",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-04-11 12:43:09.456449",
|
||||
"modified_by": "Administrator",
|
||||
"module": "ERPNext Integrations",
|
||||
"name": "Shopify Webhook Detail",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class ShopifyWebhookDetail(Document):
|
||||
pass
|
@ -40,16 +40,6 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Shopify Settings",
|
||||
"link_to": "Shopify Settings",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
@ -113,4 +103,4 @@
|
||||
"pin_to_bottom": 0,
|
||||
"pin_to_top": 0,
|
||||
"shortcuts": []
|
||||
}
|
||||
}
|
||||
|
@ -30,16 +30,6 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Shopify Settings",
|
||||
"link_to": "Shopify Settings",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
@ -79,4 +69,4 @@
|
||||
"pin_to_bottom": 0,
|
||||
"pin_to_top": 0,
|
||||
"shortcuts": []
|
||||
}
|
||||
}
|
||||
|
@ -341,7 +341,6 @@ scheduler_events = {
|
||||
"erpnext.projects.doctype.project.project.hourly_reminder",
|
||||
"erpnext.projects.doctype.project.project.collect_project_status",
|
||||
"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
|
||||
"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders",
|
||||
"erpnext.support.doctype.service_level_agreement.service_level_agreement.set_service_level_agreement_variance"
|
||||
],
|
||||
"hourly_long": [
|
||||
@ -438,7 +437,8 @@ regional_overrides = {
|
||||
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
|
||||
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
|
||||
'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields',
|
||||
'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount'
|
||||
'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount',
|
||||
'erpnext.stock.doctype.item.item.set_item_tax_from_hsn_code': 'erpnext.regional.india.utils.set_item_tax_from_hsn_code'
|
||||
},
|
||||
'United Arab Emirates': {
|
||||
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data',
|
||||
|
@ -14,7 +14,7 @@ frappe.ui.form.on('Loan Application', {
|
||||
refresh: function(frm) {
|
||||
frm.trigger("toggle_fields");
|
||||
frm.trigger("add_toolbar_buttons");
|
||||
frm.set_query("loan_type", () => {
|
||||
frm.set_query('loan_type', () => {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
|
@ -717,9 +717,8 @@ def get_bom_item_rate(args, bom_doc):
|
||||
"ignore_conversion_rate": True
|
||||
})
|
||||
item_doc = frappe.get_cached_doc("Item", args.get("item_code"))
|
||||
out = frappe._dict()
|
||||
get_price_list_rate(bom_args, item_doc, out)
|
||||
rate = out.price_list_rate
|
||||
price_list_data = get_price_list_rate(bom_args, item_doc)
|
||||
rate = price_list_data.price_list_rate
|
||||
|
||||
return rate
|
||||
|
||||
@ -748,7 +747,7 @@ def get_valuation_rate(args):
|
||||
if valuation_rate <= 0:
|
||||
last_valuation_rate = frappe.db.sql("""select valuation_rate
|
||||
from `tabStock Ledger Entry`
|
||||
where item_code = %s and valuation_rate > 0
|
||||
where item_code = %s and valuation_rate > 0 and is_cancelled = 0
|
||||
order by posting_date desc, posting_time desc, creation desc limit 1""", args['item_code'])
|
||||
|
||||
valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
|
||||
|
@ -608,6 +608,11 @@ def make_stock_entry(source_name, target_doc=None):
|
||||
target.set_missing_values()
|
||||
target.set_stock_entry_type()
|
||||
|
||||
wo_allows_alternate_item = frappe.db.get_value("Work Order", target.work_order, "allow_alternative_item")
|
||||
for item in target.items:
|
||||
item.allow_alternative_item = int(wo_allows_alternate_item and
|
||||
frappe.get_cached_value("Item", item.item_code, "allow_alternative_item"))
|
||||
|
||||
doclist = get_mapped_doc("Job Card", source_name, {
|
||||
"Job Card": {
|
||||
"doctype": "Stock Entry",
|
||||
@ -698,4 +703,4 @@ def make_corrective_job_card(source_name, operation=None, for_operation=None, ta
|
||||
}
|
||||
}, target_doc, set_missing_values)
|
||||
|
||||
return doclist
|
||||
return doclist
|
||||
|
@ -45,7 +45,6 @@ erpnext.patches.v11_0.make_location_from_warehouse
|
||||
erpnext.patches.v11_0.make_asset_finance_book_against_old_entries
|
||||
erpnext.patches.v11_0.check_buying_selling_in_currency_exchange
|
||||
erpnext.patches.v11_0.move_item_defaults_to_child_table_for_multicompany #02-07-2018 #19-06-2019
|
||||
erpnext.patches.v11_0.refactor_erpnext_shopify #2018-09-07
|
||||
erpnext.patches.v11_0.rename_overproduction_percent_field
|
||||
erpnext.patches.v11_0.update_backflush_subcontract_rm_based_on_bom
|
||||
erpnext.patches.v11_0.inter_state_field_for_gst
|
||||
@ -143,7 +142,6 @@ erpnext.patches.v12_0.add_variant_of_in_item_attribute_table
|
||||
erpnext.patches.v12_0.rename_bank_account_field_in_journal_entry_account
|
||||
erpnext.patches.v12_0.create_default_energy_point_rules
|
||||
erpnext.patches.v12_0.set_produced_qty_field_in_sales_order_for_work_order
|
||||
erpnext.patches.v12_0.set_default_shopify_app_type
|
||||
erpnext.patches.v12_0.set_cwip_and_delete_asset_settings
|
||||
erpnext.patches.v12_0.set_expense_account_in_landed_cost_voucher_taxes
|
||||
erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings
|
||||
@ -244,7 +242,6 @@ erpnext.patches.v13_0.updates_for_multi_currency_payroll
|
||||
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
|
||||
execute:frappe.delete_doc("Report", "Quoted Item Comparison")
|
||||
erpnext.patches.v13_0.update_member_email_address
|
||||
erpnext.patches.v13_0.update_custom_fields_for_shopify
|
||||
erpnext.patches.v13_0.updates_for_multi_currency_payroll
|
||||
erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
|
||||
erpnext.patches.v13_0.update_pos_closing_entry_in_merge_log
|
||||
@ -297,3 +294,7 @@ erpnext.patches.v13_0.update_level_in_bom #1234sswef
|
||||
erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry
|
||||
erpnext.patches.v13_0.update_subscription_status_in_memberships
|
||||
erpnext.patches.v13_0.update_amt_in_work_order_required_items
|
||||
erpnext.patches.v13_0.update_export_type_for_gst
|
||||
erpnext.patches.v13_0.update_tds_check_field #3
|
||||
erpnext.patches.v13_0.add_custom_field_for_south_africa
|
||||
erpnext.patches.v13_0.shopify_deprecation_warning
|
||||
|
@ -1,43 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.installer import remove_from_installed_apps
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_settings')
|
||||
frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_tax_account')
|
||||
frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_log')
|
||||
frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_webhook_detail')
|
||||
|
||||
if 'erpnext_shopify' in frappe.get_installed_apps():
|
||||
remove_from_installed_apps('erpnext_shopify')
|
||||
|
||||
frappe.delete_doc("Module Def", 'erpnext_shopify')
|
||||
|
||||
frappe.db.commit()
|
||||
|
||||
frappe.db.sql("truncate `tabShopify Log`")
|
||||
|
||||
setup_app_type()
|
||||
else:
|
||||
disable_shopify()
|
||||
|
||||
def setup_app_type():
|
||||
try:
|
||||
shopify_settings = frappe.get_doc("Shopify Settings")
|
||||
shopify_settings.app_type = 'Private'
|
||||
shopify_settings.update_price_in_erpnext_price_list = 0 if getattr(shopify_settings, 'push_prices_to_shopify', None) else 1
|
||||
shopify_settings.flags.ignore_mandatory = True
|
||||
shopify_settings.ignore_permissions = True
|
||||
shopify_settings.save()
|
||||
except Exception:
|
||||
frappe.db.set_value("Shopify Settings", None, "enable_shopify", 0)
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
|
||||
def disable_shopify():
|
||||
# due to frappe.db.set_value wrongly written and enable_shopify being default 1
|
||||
# Shopify Settings isn't properly configured and leads to error
|
||||
shopify = frappe.get_doc('Shopify Settings')
|
||||
|
||||
if shopify.app_type == "Public" or shopify.app_type == None or \
|
||||
(shopify.enable_shopify and not (shopify.shopify_url or shopify.api_key)):
|
||||
frappe.db.set_value("Shopify Settings", None, "enable_shopify", 0)
|
@ -1,6 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_settings')
|
||||
frappe.db.set_value('Shopify Settings', None, 'app_type', 'Private')
|
13
erpnext/patches/v13_0/add_custom_field_for_south_africa.py
Normal file
13
erpnext/patches/v13_0/add_custom_field_for_south_africa.py
Normal file
@ -0,0 +1,13 @@
|
||||
# Copyright (c) 2020, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from erpnext.regional.south_africa.setup import make_custom_fields
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all('Company', filters = {'country': 'South Africa'})
|
||||
if not company:
|
||||
return
|
||||
|
||||
make_custom_fields()
|
@ -30,19 +30,20 @@ def execute():
|
||||
return
|
||||
|
||||
repost_stock_entries = []
|
||||
|
||||
stock_entries = frappe.db.sql_list('''
|
||||
SELECT
|
||||
se.name
|
||||
FROM
|
||||
`tabStock Entry` se
|
||||
WHERE
|
||||
se.purpose = 'Manufacture' and se.docstatus < 2 and se.work_order in {work_orders}
|
||||
se.purpose = 'Manufacture' and se.docstatus < 2 and se.work_order in %s
|
||||
and not exists(
|
||||
select name from `tabStock Entry Detail` sed where sed.parent = se.name and sed.is_finished_item = 1
|
||||
)
|
||||
Order BY
|
||||
ORDER BY
|
||||
se.posting_date, se.posting_time
|
||||
'''.format(work_orders=tuple(work_orders)))
|
||||
''', (work_orders,))
|
||||
|
||||
if stock_entries:
|
||||
print('Length of stock entries', len(stock_entries))
|
||||
|
10
erpnext/patches/v13_0/shopify_deprecation_warning.py
Normal file
10
erpnext/patches/v13_0/shopify_deprecation_warning.py
Normal file
@ -0,0 +1,10 @@
|
||||
import click
|
||||
|
||||
|
||||
def execute():
|
||||
|
||||
click.secho(
|
||||
"Shopify Integration is moved to a separate app and will be removed from ERPNext in version-14.\n"
|
||||
"Please install the app to continue using the integration: https://github.com/frappe/ecommerce_integrations",
|
||||
fg="yellow",
|
||||
)
|
@ -1,10 +0,0 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import setup_custom_fields
|
||||
|
||||
def execute():
|
||||
if frappe.db.get_single_value('Shopify Settings', 'enable_shopify'):
|
||||
setup_custom_fields()
|
24
erpnext/patches/v13_0/update_export_type_for_gst.py
Normal file
24
erpnext/patches/v13_0/update_export_type_for_gst.py
Normal file
@ -0,0 +1,24 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all('Company', filters = {'country': 'India'})
|
||||
if not company:
|
||||
return
|
||||
|
||||
# Update custom fields
|
||||
fieldname = frappe.db.get_value('Custom Field', {'dt': 'Customer', 'fieldname': 'export_type'})
|
||||
if fieldname:
|
||||
frappe.db.set_value('Custom Field', fieldname, 'default', '')
|
||||
|
||||
fieldname = frappe.db.get_value('Custom Field', {'dt': 'Supplier', 'fieldname': 'export_type'})
|
||||
if fieldname:
|
||||
frappe.db.set_value('Custom Field', fieldname, 'default', '')
|
||||
|
||||
# Update Customer/Supplier Masters
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabCustomer` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas', 'Deemed Export')
|
||||
""")
|
||||
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabSupplier` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas')
|
||||
""")
|
9
erpnext/patches/v13_0/update_tds_check_field.py
Normal file
9
erpnext/patches/v13_0/update_tds_check_field.py
Normal file
@ -0,0 +1,9 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
if frappe.db.has_table("Tax Withholding Category") \
|
||||
and frappe.db.has_column("Tax Withholding Category", "round_off_tax_amount"):
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabTax Withholding Category` set round_off_tax_amount = 0
|
||||
WHERE round_off_tax_amount IS NULL
|
||||
""")
|
@ -112,11 +112,11 @@ class AdditionalSalary(Document):
|
||||
no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1
|
||||
return amount_per_day * no_of_days
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_additional_salaries(employee, start_date, end_date, component_type):
|
||||
additional_salary_list = frappe.db.sql("""
|
||||
select name, salary_component as component, type, amount,
|
||||
overwrite_salary_structure_amount as overwrite,
|
||||
deduct_full_tax_on_selected_payroll_date
|
||||
select name, salary_component as component, type, amount, overwrite_salary_structure_amount as overwrite,
|
||||
deduct_full_tax_on_selected_payroll_date, is_recurring
|
||||
from `tabAdditional Salary`
|
||||
where employee=%(employee)s
|
||||
and docstatus = 1
|
||||
|
@ -12,6 +12,7 @@
|
||||
"year_to_date",
|
||||
"section_break_5",
|
||||
"additional_salary",
|
||||
"is_recurring_additional_salary",
|
||||
"statistical_component",
|
||||
"depends_on_payment_days",
|
||||
"exempted_from_income_tax",
|
||||
@ -235,11 +236,19 @@
|
||||
"label": "Year To Date",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.parenttype=='Salary Slip' && doc.parentfield=='earnings' && doc.additional_salary",
|
||||
"fieldname": "is_recurring_additional_salary",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Recurring Additional Salary",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-14 13:39:15.847158",
|
||||
"modified": "2021-03-14 13:39:15.847158",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Salary Detail",
|
||||
|
@ -7,12 +7,12 @@ import datetime, math
|
||||
|
||||
from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate, get_first_day
|
||||
from frappe.model.naming import make_autoname
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
|
||||
from frappe import msgprint, _
|
||||
from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_dates
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
from erpnext.utilities.transaction_base import TransactionBase
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salaries
|
||||
from erpnext.payroll.doctype.payroll_period.payroll_period import get_period_factor, get_payroll_period
|
||||
from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount
|
||||
@ -618,7 +618,8 @@ class SalarySlip(TransactionBase):
|
||||
get_salary_component_data(additional_salary.component),
|
||||
additional_salary.amount,
|
||||
component_type,
|
||||
additional_salary
|
||||
additional_salary,
|
||||
is_recurring = additional_salary.is_recurring
|
||||
)
|
||||
|
||||
def add_tax_components(self, payroll_period):
|
||||
@ -639,17 +640,20 @@ class SalarySlip(TransactionBase):
|
||||
tax_row = get_salary_component_data(d)
|
||||
self.update_component_row(tax_row, tax_amount, "deductions")
|
||||
|
||||
def update_component_row(self, component_data, amount, component_type, additional_salary=None):
|
||||
def update_component_row(self, component_data, amount, component_type, additional_salary=None, is_recurring = 0):
|
||||
component_row = None
|
||||
for d in self.get(component_type):
|
||||
if d.salary_component != component_data.salary_component:
|
||||
continue
|
||||
|
||||
if (
|
||||
(not d.additional_salary
|
||||
and (not additional_salary or additional_salary.overwrite))
|
||||
or (additional_salary
|
||||
and additional_salary.name == d.additional_salary)
|
||||
(
|
||||
not d.additional_salary
|
||||
and (not additional_salary or additional_salary.overwrite)
|
||||
) or (
|
||||
additional_salary
|
||||
and additional_salary.name == d.additional_salary
|
||||
)
|
||||
):
|
||||
component_row = d
|
||||
break
|
||||
@ -677,8 +681,13 @@ class SalarySlip(TransactionBase):
|
||||
component_row.set(attr, component_data.get(attr))
|
||||
|
||||
if additional_salary:
|
||||
component_row.default_amount = 0
|
||||
component_row.additional_amount = amount
|
||||
component_row.is_recurring_additional_salary = is_recurring
|
||||
if additional_salary.overwrite:
|
||||
component_row.additional_amount = flt(flt(amount) - flt(component_row.get("default_amount", 0)),
|
||||
component_row.precision("additional_amount"))
|
||||
else:
|
||||
component_row.default_amount = 0
|
||||
component_row.additional_amount = amount
|
||||
component_row.additional_salary = additional_salary.name
|
||||
component_row.deduct_full_tax_on_selected_payroll_date = \
|
||||
additional_salary.deduct_full_tax_on_selected_payroll_date
|
||||
@ -710,6 +719,7 @@ class SalarySlip(TransactionBase):
|
||||
# get remaining numbers of sub-period (period for which one salary is processed)
|
||||
remaining_sub_periods = get_period_factor(self.employee,
|
||||
self.start_date, self.end_date, self.payroll_frequency, payroll_period)[1]
|
||||
|
||||
# get taxable_earnings, paid_taxes for previous period
|
||||
previous_taxable_earnings = self.get_taxable_earnings_for_prev_period(payroll_period.start_date,
|
||||
self.start_date, tax_slab.allow_tax_exemption)
|
||||
@ -869,8 +879,16 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
if earning.is_tax_applicable:
|
||||
if additional_amount:
|
||||
taxable_earnings += (amount - additional_amount)
|
||||
additional_income += additional_amount
|
||||
if not earning.is_recurring_additional_salary:
|
||||
taxable_earnings += (amount - additional_amount)
|
||||
additional_income += additional_amount
|
||||
else:
|
||||
to_date = frappe.db.get_value("Additional Salary", earning.additional_salary, 'to_date')
|
||||
period = (getdate(to_date).month - getdate(self.start_date).month) + 1
|
||||
if period > 0:
|
||||
taxable_earnings += (amount - additional_amount) * period
|
||||
additional_income += additional_amount * period
|
||||
|
||||
if earning.deduct_full_tax_on_selected_payroll_date:
|
||||
additional_income_with_full_tax += additional_amount
|
||||
continue
|
||||
|
@ -129,7 +129,7 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None,
|
||||
"earnings": make_earning_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
|
||||
"deductions": make_deduction_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
|
||||
"payroll_frequency": payroll_frequency,
|
||||
"payment_account": get_random("Account", filters={"account_currency": currency}),
|
||||
"payment_account": get_random("Account", filters={'account_currency': currency}),
|
||||
"currency": currency
|
||||
}
|
||||
if other_details and isinstance(other_details, dict):
|
||||
|
5
erpnext/regional/address_template/templates/france.html
Normal file
5
erpnext/regional/address_template/templates/france.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% if address_line1 %}{{ address_line1 }}{% endif -%}
|
||||
{% if address_line2 %}<br>{{ address_line2 }}{% endif -%}
|
||||
{% if pincode %}<br>{{ pincode }}{% endif -%}
|
||||
{% if city %} {{ city }}{% endif -%}
|
||||
{% if country %}<br>{{ country }}{% endif -%}
|
@ -280,9 +280,15 @@ class GSTR3BReport(Document):
|
||||
if self.get('invoice_items'):
|
||||
# Build itemised tax for export invoices, nil and exempted where tax table is blank
|
||||
for invoice, items in iteritems(self.invoice_items):
|
||||
if invoice not in self.items_based_on_tax_rate and (self.invoice_detail_map.get(invoice, {}).get('export_type')
|
||||
== "Without Payment of Tax"):
|
||||
if invoice not in self.items_based_on_tax_rate and self.invoice_detail_map.get(invoice, {}).get('export_type') \
|
||||
== "Without Payment of Tax" and self.invoice_detail_map.get(invoice, {}).get('gst_category') == "Overseas":
|
||||
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
|
||||
else:
|
||||
for item in items.keys():
|
||||
if item in self.is_nil_exempt + self.is_non_gst and \
|
||||
item not in self.items_based_on_tax_rate.get(invoice, {}).get(0, []):
|
||||
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, [])
|
||||
self.items_based_on_tax_rate[invoice][0].append(item)
|
||||
|
||||
def set_outward_taxable_supplies(self):
|
||||
inter_state_supply_details = {}
|
||||
|
@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('South Africa VAT Settings', {
|
||||
refresh: function(frm) {
|
||||
frm.set_query("company", function() {
|
||||
return {
|
||||
filters: {
|
||||
country: "South Africa",
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.set_query("account", "vat_accounts", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
account_type: "Tax",
|
||||
is_group: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
@ -0,0 +1,76 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "field:company",
|
||||
"creation": "2021-07-08 22:34:33.668015",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company",
|
||||
"vat_accounts"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "vat_accounts",
|
||||
"fieldtype": "Table",
|
||||
"label": "VAT Accounts",
|
||||
"options": "South Africa VAT Account",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-14 02:17:52.476762",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Regional",
|
||||
"name": "South Africa VAT Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Auditor",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class SouthAfricaVATSettings(Document):
|
||||
pass
|
@ -0,0 +1,8 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestSouthAfricaVATSettings(unittest.TestCase):
|
||||
pass
|
@ -316,10 +316,6 @@ def get_payment_details(invoice):
|
||||
))
|
||||
|
||||
def get_return_doc_reference(invoice):
|
||||
if not invoice.return_against:
|
||||
frappe.throw(_('For generating IRN, reference to the original invoice is mandatory for a credit note. Please set {} field to generate e-invoice.')
|
||||
.format(frappe.bold('Return Against')), title=_('Missing Field'))
|
||||
|
||||
invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date')
|
||||
return frappe._dict(dict(
|
||||
invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy')
|
||||
@ -438,7 +434,7 @@ def make_einvoice(invoice):
|
||||
if invoice.is_pos and invoice.base_paid_amount:
|
||||
payment_details = get_payment_details(invoice)
|
||||
|
||||
if invoice.is_return:
|
||||
if invoice.is_return and invoice.return_against:
|
||||
prev_doc_details = get_return_doc_reference(invoice)
|
||||
|
||||
if invoice.transporter and not invoice.is_return:
|
||||
@ -969,7 +965,7 @@ class GSPConnector():
|
||||
"attached_to_doctype": doctype,
|
||||
"attached_to_name": docname,
|
||||
"attached_to_field": "qrcode_image",
|
||||
"is_private": 1,
|
||||
"is_private": 0,
|
||||
"content": qr_image.getvalue()})
|
||||
_file.save()
|
||||
frappe.db.commit()
|
||||
|
@ -641,7 +641,6 @@ def make_custom_fields(update=True):
|
||||
'label': 'Export Type',
|
||||
'fieldtype': 'Select',
|
||||
'insert_after': 'gst_category',
|
||||
'default': 'Without Payment of Tax',
|
||||
'depends_on':'eval:in_list(["SEZ", "Overseas"], doc.gst_category)',
|
||||
'options': '\nWith Payment of Tax\nWithout Payment of Tax'
|
||||
}
|
||||
@ -660,7 +659,6 @@ def make_custom_fields(update=True):
|
||||
'label': 'Export Type',
|
||||
'fieldtype': 'Select',
|
||||
'insert_after': 'gst_category',
|
||||
'default': 'Without Payment of Tax',
|
||||
'depends_on':'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)',
|
||||
'options': '\nWith Payment of Tax\nWithout Payment of Tax'
|
||||
}
|
||||
|
@ -859,4 +859,15 @@ def get_depreciation_amount(asset, depreciable_value, row):
|
||||
|
||||
depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100))
|
||||
|
||||
return depreciation_amount
|
||||
return depreciation_amount
|
||||
|
||||
def set_item_tax_from_hsn_code(item):
|
||||
if not item.taxes and item.gst_hsn_code:
|
||||
hsn_doc = frappe.get_doc("GST HSN Code", item.gst_hsn_code)
|
||||
|
||||
for tax in hsn_doc.taxes:
|
||||
item.append('taxes', {
|
||||
'item_tax_template': tax.item_tax_template,
|
||||
'tax_category': tax.tax_category,
|
||||
'valid_from': tax.valid_from
|
||||
})
|
@ -286,7 +286,8 @@ class Gstr1Report(object):
|
||||
# Build itemised tax for export invoices where tax table is blank
|
||||
for invoice, items in iteritems(self.invoice_items):
|
||||
if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \
|
||||
and frappe.db.get_value(self.doctype, invoice, "export_type") == "Without Payment of Tax":
|
||||
and self.invoices.get(invoice, {}).get('export_type') == "Without Payment of Tax" \
|
||||
and self.invoices.get(invoice, {}).get('gst_category') == "Overseas":
|
||||
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
|
||||
|
||||
def get_columns(self):
|
||||
|
31
erpnext/regional/report/vat_audit_report/vat_audit_report.js
Normal file
31
erpnext/regional/report/vat_audit_report/vat_audit_report.js
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["VAT Audit Report"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"reqd": 1,
|
||||
"default": frappe.defaults.get_user_default("Company")
|
||||
},
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"reqd": 1,
|
||||
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -2),
|
||||
"width": "80"
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"reqd": 1,
|
||||
"default": frappe.datetime.get_today()
|
||||
}
|
||||
]
|
||||
};
|
@ -0,0 +1,32 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2021-07-09 11:07:43.473518",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2021-07-09 11:07:43.473518",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Regional",
|
||||
"name": "VAT Audit Report",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "GL Entry",
|
||||
"report_name": "VAT Audit Report",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"role": "Auditor"
|
||||
}
|
||||
]
|
||||
}
|
253
erpnext/regional/report/vat_audit_report/vat_audit_report.py
Normal file
253
erpnext/regional/report/vat_audit_report/vat_audit_report.py
Normal file
@ -0,0 +1,253 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
from frappe import _
|
||||
from frappe.utils import formatdate
|
||||
|
||||
def execute(filters=None):
|
||||
return VATAuditReport(filters).run()
|
||||
|
||||
class VATAuditReport(object):
|
||||
|
||||
def __init__(self, filters=None):
|
||||
self.filters = frappe._dict(filters or {})
|
||||
self.columns = []
|
||||
self.data = []
|
||||
self.doctypes = ["Purchase Invoice", "Sales Invoice"]
|
||||
|
||||
def run(self):
|
||||
self.get_sa_vat_accounts()
|
||||
self.get_columns()
|
||||
for doctype in self.doctypes:
|
||||
self.select_columns = """
|
||||
name as voucher_no,
|
||||
posting_date, remarks"""
|
||||
columns = ", supplier as party, credit_to as account" if doctype=="Purchase Invoice" \
|
||||
else ", customer as party, debit_to as account"
|
||||
self.select_columns += columns
|
||||
|
||||
self.get_invoice_data(doctype)
|
||||
|
||||
if self.invoices:
|
||||
self.get_invoice_items(doctype)
|
||||
self.get_items_based_on_tax_rate(doctype)
|
||||
self.get_data(doctype)
|
||||
|
||||
return self.columns, self.data
|
||||
|
||||
def get_sa_vat_accounts(self):
|
||||
self.sa_vat_accounts = frappe.get_list("South Africa VAT Account",
|
||||
filters = {"parent": self.filters.company}, pluck="account")
|
||||
if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate:
|
||||
frappe.throw(_("Please set VAT Accounts in South Africa VAT Settings"))
|
||||
|
||||
def get_invoice_data(self, doctype):
|
||||
conditions = self.get_conditions()
|
||||
self.invoices = frappe._dict()
|
||||
|
||||
invoice_data = frappe.db.sql("""
|
||||
SELECT
|
||||
{select_columns}
|
||||
FROM
|
||||
`tab{doctype}`
|
||||
WHERE
|
||||
docstatus = 1 {where_conditions}
|
||||
and is_opening = "No"
|
||||
ORDER BY
|
||||
posting_date DESC
|
||||
""".format(select_columns=self.select_columns, doctype=doctype,
|
||||
where_conditions=conditions), self.filters, as_dict=1)
|
||||
|
||||
for d in invoice_data:
|
||||
self.invoices.setdefault(d.voucher_no, d)
|
||||
|
||||
def get_invoice_items(self, doctype):
|
||||
self.invoice_items = frappe._dict()
|
||||
|
||||
items = frappe.db.sql("""
|
||||
SELECT
|
||||
item_code, parent, taxable_value, base_net_amount, is_zero_rated
|
||||
FROM
|
||||
`tab%s Item`
|
||||
WHERE
|
||||
parent in (%s)
|
||||
""" % (doctype, ", ".join(["%s"]*len(self.invoices))), tuple(self.invoices), as_dict=1)
|
||||
for d in items:
|
||||
if d.item_code not in self.invoice_items.get(d.parent, {}):
|
||||
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, {
|
||||
'net_amount': 0.0})
|
||||
self.invoice_items[d.parent][d.item_code]['net_amount'] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
|
||||
self.invoice_items[d.parent][d.item_code]['is_zero_rated'] = d.is_zero_rated
|
||||
|
||||
def get_items_based_on_tax_rate(self, doctype):
|
||||
self.items_based_on_tax_rate = frappe._dict()
|
||||
self.item_tax_rate = frappe._dict()
|
||||
self.tax_doctype = "Purchase Taxes and Charges" if doctype=="Purchase Invoice" \
|
||||
else "Sales Taxes and Charges"
|
||||
|
||||
self.tax_details = frappe.db.sql("""
|
||||
SELECT
|
||||
parent, account_head, item_wise_tax_detail, base_tax_amount_after_discount_amount
|
||||
FROM
|
||||
`tab%s`
|
||||
WHERE
|
||||
parenttype = %s and docstatus = 1
|
||||
and parent in (%s)
|
||||
ORDER BY
|
||||
account_head
|
||||
""" % (self.tax_doctype, "%s", ", ".join(["%s"]*len(self.invoices.keys()))),
|
||||
tuple([doctype] + list(self.invoices.keys())))
|
||||
|
||||
for parent, account, item_wise_tax_detail, tax_amount in self.tax_details:
|
||||
if item_wise_tax_detail:
|
||||
try:
|
||||
if account in self.sa_vat_accounts:
|
||||
item_wise_tax_detail = json.loads(item_wise_tax_detail)
|
||||
else:
|
||||
continue
|
||||
for item_code, taxes in item_wise_tax_detail.items():
|
||||
is_zero_rated = self.invoice_items.get(parent).get(item_code).get("is_zero_rated")
|
||||
#to skip items with non-zero tax rate in multiple rows
|
||||
if taxes[0] == 0 and not is_zero_rated:
|
||||
continue
|
||||
tax_rate, item_amount_map = self.get_item_amount_map(parent, item_code, taxes)
|
||||
|
||||
if tax_rate is not None:
|
||||
rate_based_dict = self.items_based_on_tax_rate.setdefault(parent, {}) \
|
||||
.setdefault(tax_rate, [])
|
||||
if item_code not in rate_based_dict:
|
||||
rate_based_dict.append(item_code)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
def get_item_amount_map(self, parent, item_code, taxes):
|
||||
net_amount = self.invoice_items.get(parent).get(item_code).get("net_amount")
|
||||
tax_rate = taxes[0]
|
||||
tax_amount = taxes[1]
|
||||
gross_amount = net_amount + tax_amount
|
||||
item_amount_map = self.item_tax_rate.setdefault(parent, {}) \
|
||||
.setdefault(item_code, [])
|
||||
amount_dict = {
|
||||
"tax_rate": tax_rate,
|
||||
"gross_amount": gross_amount,
|
||||
"tax_amount": tax_amount,
|
||||
"net_amount": net_amount
|
||||
}
|
||||
item_amount_map.append(amount_dict)
|
||||
|
||||
return tax_rate, item_amount_map
|
||||
|
||||
def get_conditions(self):
|
||||
conditions = ""
|
||||
for opts in (("company", " and company=%(company)s"),
|
||||
("from_date", " and posting_date>=%(from_date)s"),
|
||||
("to_date", " and posting_date<=%(to_date)s")):
|
||||
if self.filters.get(opts[0]):
|
||||
conditions += opts[1]
|
||||
|
||||
return conditions
|
||||
|
||||
def get_data(self, doctype):
|
||||
consolidated_data = self.get_consolidated_data(doctype)
|
||||
section_name = _("Purchases") if doctype == "Purchase Invoice" else _("Sales")
|
||||
|
||||
for rate, section in consolidated_data.items():
|
||||
rate = int(rate)
|
||||
label = frappe.bold(section_name + "- " + "Rate" + " " + str(rate) + "%")
|
||||
section_head = {"posting_date": label}
|
||||
total_gross = total_tax = total_net = 0
|
||||
self.data.append(section_head)
|
||||
for row in section.get("data"):
|
||||
self.data.append(row)
|
||||
total_gross += row["gross_amount"]
|
||||
total_tax += row["tax_amount"]
|
||||
total_net += row["net_amount"]
|
||||
|
||||
total = {
|
||||
"posting_date": frappe.bold(_("Total")),
|
||||
"gross_amount": total_gross,
|
||||
"tax_amount": total_tax,
|
||||
"net_amount": total_net,
|
||||
"bold":1
|
||||
}
|
||||
self.data.append(total)
|
||||
self.data.append({})
|
||||
|
||||
def get_consolidated_data(self, doctype):
|
||||
consolidated_data_map={}
|
||||
for inv, inv_data in self.invoices.items():
|
||||
if self.items_based_on_tax_rate.get(inv):
|
||||
for rate, items in self.items_based_on_tax_rate.get(inv).items():
|
||||
consolidated_data_map.setdefault(rate, {"data": []})
|
||||
for item in items:
|
||||
row = {}
|
||||
item_details = self.item_tax_rate.get(inv).get(item)
|
||||
row["account"] = inv_data.get("account")
|
||||
row["posting_date"] = formatdate(inv_data.get("posting_date"), "dd-mm-yyyy")
|
||||
row["voucher_type"] = doctype
|
||||
row["voucher_no"] = inv
|
||||
row["remarks"] = inv_data.get("remarks")
|
||||
row["gross_amount"]= item_details[0].get("gross_amount")
|
||||
row["tax_amount"]= item_details[0].get("tax_amount")
|
||||
row["net_amount"]= item_details[0].get("net_amount")
|
||||
consolidated_data_map[rate]["data"].append(row)
|
||||
|
||||
return consolidated_data_map
|
||||
|
||||
def get_columns(self):
|
||||
self.columns = [
|
||||
{
|
||||
"fieldname": "posting_date",
|
||||
"label": "Posting Date",
|
||||
"fieldtype": "Data",
|
||||
"width": 200
|
||||
},
|
||||
{
|
||||
"fieldname": "account",
|
||||
"label": "Account",
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_type",
|
||||
"label": "Voucher Type",
|
||||
"fieldtype": "Data",
|
||||
"width": 140,
|
||||
"hidden": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_no",
|
||||
"label": "Reference",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "voucher_type",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"fieldname": "remarks",
|
||||
"label": "Details",
|
||||
"fieldtype": "Data",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"fieldname": "net_amount",
|
||||
"label": "Net Amount",
|
||||
"fieldtype": "Currency",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_amount",
|
||||
"label": "Tax Amount",
|
||||
"fieldtype": "Currency",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"fieldname": "gross_amount",
|
||||
"label": "Gross Amount",
|
||||
"fieldtype": "Currency",
|
||||
"width": 150
|
||||
},
|
||||
]
|
36
erpnext/regional/south_africa/setup.py
Normal file
36
erpnext/regional/south_africa/setup.py
Normal file
@ -0,0 +1,36 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe, os, json
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
from frappe.permissions import add_permission, update_permission_property
|
||||
|
||||
def setup(company=None, patch=True):
|
||||
add_permissions()
|
||||
|
||||
def make_custom_fields(update=True):
|
||||
is_zero_rated = dict(fieldname='is_zero_rated', label='Is Zero Rated',
|
||||
fieldtype='Check', fetch_from='item_code.is_zero_rated',
|
||||
insert_after='description', print_hide=1)
|
||||
custom_fields = {
|
||||
'Item': [
|
||||
dict(fieldname='is_zero_rated', label='Is Zero Rated',
|
||||
fieldtype='Check', insert_after='item_group',
|
||||
print_hide=1)
|
||||
],
|
||||
'Sales Invoice Item': is_zero_rated,
|
||||
'Purchase Invoice Item': is_zero_rated
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields, update=update)
|
||||
|
||||
def add_permissions():
|
||||
"""Add Permissions for South Africa VAT Settings and South Africa VAT Account"""
|
||||
for doctype in ('South Africa VAT Settings', 'South Africa VAT Account'):
|
||||
add_permission(doctype, 'All', 0)
|
||||
for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
|
||||
add_permission(doctype, role, 0)
|
||||
update_permission_property(doctype, role, 0, 'write', 1)
|
||||
update_permission_property(doctype, role, 0, 'create', 1)
|
@ -564,7 +564,6 @@ erpnext.PointOfSale.ItemCart = class {
|
||||
)
|
||||
|
||||
set_dynamic_rate_header_width();
|
||||
this.scroll_to_item($item_to_update);
|
||||
|
||||
function set_dynamic_rate_header_width() {
|
||||
const rate_cols = Array.from(me.$cart_items_wrapper.find(".item-rate-amount"));
|
||||
@ -639,12 +638,6 @@ erpnext.PointOfSale.ItemCart = class {
|
||||
$($img).parent().replaceWith(`<div class="item-image item-abbr">${item_abbr}</div>`);
|
||||
}
|
||||
|
||||
scroll_to_item($item) {
|
||||
if ($item.length === 0) return;
|
||||
const scrollTop = $item.offset().top - this.$cart_items_wrapper.offset().top + this.$cart_items_wrapper.scrollTop();
|
||||
this.$cart_items_wrapper.animate({ scrollTop });
|
||||
}
|
||||
|
||||
update_selector_value_in_cart_item(selector, value, item) {
|
||||
const $item_to_update = this.get_cart_item(item);
|
||||
$item_to_update.attr(`data-${selector}`, escape(value));
|
||||
|
@ -198,6 +198,7 @@ erpnext.PointOfSale.Payment = class {
|
||||
const is_cash_shortcuts_invisible = !this.$payment_modes.find('.cash-shortcuts').is(':visible');
|
||||
this.attach_cash_shortcuts(frm.doc);
|
||||
!is_cash_shortcuts_invisible && this.$payment_modes.find('.cash-shortcuts').css('display', 'grid');
|
||||
this.render_payment_mode_dom();
|
||||
});
|
||||
|
||||
frappe.ui.form.on('POS Invoice', 'loyalty_amount', (frm) => {
|
||||
|
@ -62,12 +62,12 @@ class TestCurrencyExchange(unittest.TestCase):
|
||||
|
||||
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling")
|
||||
self.assertEqual(exchange_rate, 62.9)
|
||||
|
||||
# Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io
|
||||
|
||||
# Exchange rate as on 15th Dec, 2015
|
||||
self.clear_cache()
|
||||
exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_selling")
|
||||
self.assertFalse(exchange_rate == 60)
|
||||
self.assertEqual(flt(exchange_rate, 3), 66.894)
|
||||
self.assertEqual(flt(exchange_rate, 3), 66.999)
|
||||
|
||||
def test_exchange_rate_strict(self):
|
||||
# strict currency settings
|
||||
@ -77,28 +77,17 @@ class TestCurrencyExchange(unittest.TestCase):
|
||||
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01", "for_buying")
|
||||
self.assertEqual(exchange_rate, 60.0)
|
||||
|
||||
# Will fetch from fixer.io
|
||||
self.clear_cache()
|
||||
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying")
|
||||
self.assertEqual(flt(exchange_rate, 3), 67.79)
|
||||
self.assertEqual(flt(exchange_rate, 3), 67.235)
|
||||
|
||||
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling")
|
||||
self.assertEqual(exchange_rate, 62.9)
|
||||
|
||||
# Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io
|
||||
# Exchange rate as on 15th Dec, 2015
|
||||
self.clear_cache()
|
||||
exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_buying")
|
||||
self.assertEqual(flt(exchange_rate, 3), 66.894)
|
||||
|
||||
exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-10", "for_selling")
|
||||
self.assertEqual(exchange_rate, 65.1)
|
||||
|
||||
# NGN is not available on fixer.io so these should return 0
|
||||
exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-09", "for_selling")
|
||||
self.assertEqual(exchange_rate, 0)
|
||||
|
||||
exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-11", "for_selling")
|
||||
self.assertEqual(exchange_rate, 0)
|
||||
self.assertEqual(flt(exchange_rate, 3), 66.999)
|
||||
|
||||
def test_exchange_rate_strict_switched(self):
|
||||
# Start with allow_stale is True
|
||||
@ -111,4 +100,4 @@ class TestCurrencyExchange(unittest.TestCase):
|
||||
# Will fetch from fixer.io
|
||||
self.clear_cache()
|
||||
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying")
|
||||
self.assertEqual(flt(exchange_rate, 3), 67.79)
|
||||
self.assertEqual(flt(exchange_rate, 3), 67.235)
|
||||
|
@ -54,7 +54,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-08 23:13:48.049879",
|
||||
"modified": "2021-08-04 20:15:59.071493",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Transaction Deletion Record",
|
||||
@ -70,6 +70,7 @@
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
|
@ -18,7 +18,7 @@ class TransactionDeletionRecord(Document):
|
||||
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
|
||||
for doctype in self.doctypes_to_be_ignored:
|
||||
if doctype.doctype_name not in doctypes_to_be_ignored_list:
|
||||
frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it. "),
|
||||
frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it."),
|
||||
title=_("Not Allowed"))
|
||||
|
||||
def before_submit(self):
|
||||
@ -31,7 +31,7 @@ class TransactionDeletionRecord(Document):
|
||||
clear_notifications()
|
||||
self.delete_company_transactions()
|
||||
|
||||
def populate_doctypes_to_be_ignored_table(self):
|
||||
def populate_doctypes_to_be_ignored_table(self):
|
||||
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
|
||||
for doctype in doctypes_to_be_ignored_list:
|
||||
self.append('doctypes_to_be_ignored', {
|
||||
@ -74,7 +74,7 @@ class TransactionDeletionRecord(Document):
|
||||
doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list()
|
||||
docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list)
|
||||
|
||||
tables = self.get_all_child_doctypes()
|
||||
tables = self.get_all_child_doctypes()
|
||||
for docfield in docfields:
|
||||
if docfield['parent'] != self.doctype:
|
||||
no_of_docs = self.get_number_of_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname'])
|
||||
@ -90,7 +90,7 @@ class TransactionDeletionRecord(Document):
|
||||
naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname')
|
||||
if naming_series:
|
||||
if '#' in naming_series:
|
||||
self.update_naming_series(naming_series, docfield['parent'])
|
||||
self.update_naming_series(naming_series, docfield['parent'])
|
||||
|
||||
def get_doctypes_to_be_ignored_list(self):
|
||||
singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name')
|
||||
@ -101,9 +101,9 @@ class TransactionDeletionRecord(Document):
|
||||
return doctypes_to_be_ignored_list
|
||||
|
||||
def get_doctypes_with_company_field(self, doctypes_to_be_ignored_list):
|
||||
docfields = frappe.get_all('DocField',
|
||||
docfields = frappe.get_all('DocField',
|
||||
filters = {
|
||||
'fieldtype': 'Link',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Company',
|
||||
'parent': ['not in', doctypes_to_be_ignored_list]},
|
||||
fields=['parent', 'fieldname'])
|
||||
@ -121,7 +121,7 @@ class TransactionDeletionRecord(Document):
|
||||
self.append('doctypes', {
|
||||
'doctype_name' : doctype,
|
||||
'no_of_docs' : no_of_docs
|
||||
})
|
||||
})
|
||||
|
||||
def delete_child_tables(self, doctype, company_fieldname):
|
||||
parent_docs_to_be_deleted = frappe.get_all(doctype, {
|
||||
@ -129,7 +129,7 @@ class TransactionDeletionRecord(Document):
|
||||
}, pluck = 'name')
|
||||
|
||||
child_tables = frappe.get_all('DocField', filters = {
|
||||
'fieldtype': 'Table',
|
||||
'fieldtype': 'Table',
|
||||
'parent': doctype
|
||||
}, pluck = 'options')
|
||||
|
||||
|
@ -93,21 +93,21 @@ def get_exchange_rate(from_currency, to_currency, transaction_date=None, args=No
|
||||
|
||||
try:
|
||||
cache = frappe.cache()
|
||||
key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date,from_currency, to_currency)
|
||||
key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date, from_currency, to_currency)
|
||||
value = cache.get(key)
|
||||
|
||||
if not value:
|
||||
import requests
|
||||
api_url = "https://frankfurter.app/{0}".format(transaction_date)
|
||||
api_url = "https://api.exchangerate.host/convert"
|
||||
response = requests.get(api_url, params={
|
||||
"base": from_currency,
|
||||
"symbols": to_currency
|
||||
"date": transaction_date,
|
||||
"from": from_currency,
|
||||
"to": to_currency
|
||||
})
|
||||
# expire in 6 hours
|
||||
response.raise_for_status()
|
||||
value = response.json()["rates"][to_currency]
|
||||
|
||||
cache.set_value(key, value, expires_in_sec=6 * 60 * 60)
|
||||
value = response.json()["result"]
|
||||
cache.setex(name=key, time=21600, value=flt(value))
|
||||
return flt(value)
|
||||
except:
|
||||
frappe.log_error(title="Get Exchange Rate")
|
||||
|
@ -162,19 +162,19 @@ def get_batch_qty(batch_no=None, warehouse=None, item_code=None, posting_date=No
|
||||
|
||||
out = float(frappe.db.sql("""select sum(actual_qty)
|
||||
from `tabStock Ledger Entry`
|
||||
where warehouse=%s and batch_no=%s {0}""".format(cond),
|
||||
where is_cancelled = 0 and warehouse=%s and batch_no=%s {0}""".format(cond),
|
||||
(warehouse, batch_no))[0][0] or 0)
|
||||
|
||||
if batch_no and not warehouse:
|
||||
out = frappe.db.sql('''select warehouse, sum(actual_qty) as qty
|
||||
from `tabStock Ledger Entry`
|
||||
where batch_no=%s
|
||||
where is_cancelled = 0 and batch_no=%s
|
||||
group by warehouse''', batch_no, as_dict=1)
|
||||
|
||||
if not batch_no and item_code and warehouse:
|
||||
out = frappe.db.sql('''select batch_no, sum(actual_qty) as qty
|
||||
from `tabStock Ledger Entry`
|
||||
where item_code = %s and warehouse=%s
|
||||
where is_cancelled = 0 and item_code = %s and warehouse=%s
|
||||
group by batch_no''', (item_code, warehouse), as_dict=1)
|
||||
|
||||
return out
|
||||
|
@ -269,11 +269,14 @@ class TestBatch(unittest.TestCase):
|
||||
batch2 = create_batch('_Test Batch Price Item', 300, 1)
|
||||
batch3 = create_batch('_Test Batch Price Item', 400, 0)
|
||||
|
||||
company = "_Test Company with perpetual inventory"
|
||||
currency = frappe.get_cached_value("Company", company, "default_currency")
|
||||
|
||||
args = frappe._dict({
|
||||
"item_code": "_Test Batch Price Item",
|
||||
"company": "_Test Company with perpetual inventory",
|
||||
"company": company,
|
||||
"price_list": "_Test Price List",
|
||||
"currency": "_Test Currency",
|
||||
"currency": currency,
|
||||
"doctype": "Sales Invoice",
|
||||
"conversion_rate": 1,
|
||||
"price_list_currency": "_Test Currency",
|
||||
@ -333,4 +336,4 @@ def make_new_batch(**args):
|
||||
except frappe.DuplicateEntryError:
|
||||
batch = frappe.get_doc("Batch", args.batch_id)
|
||||
|
||||
return batch
|
||||
return batch
|
||||
|
@ -138,20 +138,6 @@ frappe.ui.form.on("Item", {
|
||||
frm.toggle_reqd('customer', frm.doc.is_customer_provided_item ? 1:0);
|
||||
},
|
||||
|
||||
gst_hsn_code: function(frm) {
|
||||
if((!frm.doc.taxes || !frm.doc.taxes.length) && frm.doc.gst_hsn_code) {
|
||||
frappe.db.get_doc("GST HSN Code", frm.doc.gst_hsn_code).then(hsn_doc => {
|
||||
$.each(hsn_doc.taxes || [], function(i, tax) {
|
||||
let a = frappe.model.add_child(cur_frm.doc, 'Item Tax', 'taxes');
|
||||
a.item_tax_template = tax.item_tax_template;
|
||||
a.tax_category = tax.tax_category;
|
||||
a.valid_from = tax.valid_from;
|
||||
frm.refresh_field('taxes');
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
is_fixed_asset: function(frm) {
|
||||
// set serial no to false & toggles its visibility
|
||||
frm.set_value('has_serial_no', 0);
|
||||
|
@ -123,6 +123,7 @@ class Item(WebsiteGenerator):
|
||||
self.cant_change()
|
||||
self.update_show_in_website()
|
||||
self.validate_item_tax_net_rate_range()
|
||||
set_item_tax_from_hsn_code(self)
|
||||
|
||||
if not self.is_new():
|
||||
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
|
||||
@ -1305,3 +1306,7 @@ def update_variants(variants, template, publish_progress=True):
|
||||
def on_doctype_update():
|
||||
# since route is a Text column, it needs a length for indexing
|
||||
frappe.db.add_index("Item", ["route(500)"])
|
||||
|
||||
@erpnext.allow_regional
|
||||
def set_item_tax_from_hsn_code(item):
|
||||
pass
|
15
erpnext/stock/doctype/item/regional/india.js
Normal file
15
erpnext/stock/doctype/item/regional/india.js
Normal file
@ -0,0 +1,15 @@
|
||||
frappe.ui.form.on('Item', {
|
||||
gst_hsn_code: function(frm) {
|
||||
if ((!frm.doc.taxes || !frm.doc.taxes.length) && frm.doc.gst_hsn_code) {
|
||||
frappe.db.get_doc("GST HSN Code", frm.doc.gst_hsn_code).then(hsn_doc => {
|
||||
$.each(hsn_doc.taxes || [], function(i, tax) {
|
||||
let a = frappe.model.add_child(cur_frm.doc, 'Item Tax', 'taxes');
|
||||
a.item_tax_template = tax.item_tax_template;
|
||||
a.tax_category = tax.tax_category;
|
||||
a.valid_from = tax.valid_from;
|
||||
frm.refresh_field('taxes');
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
@ -83,14 +83,17 @@ class TestItem(unittest.TestCase):
|
||||
|
||||
make_test_objects("Item Price")
|
||||
|
||||
company = "_Test Company"
|
||||
currency = frappe.get_cached_value("Company", company, "default_currency")
|
||||
|
||||
details = get_item_details({
|
||||
"item_code": "_Test Item",
|
||||
"company": "_Test Company",
|
||||
"company": company,
|
||||
"price_list": "_Test Price List",
|
||||
"currency": "_Test Currency",
|
||||
"currency": currency,
|
||||
"doctype": "Sales Order",
|
||||
"conversion_rate": 1,
|
||||
"price_list_currency": "_Test Currency",
|
||||
"price_list_currency": currency,
|
||||
"plc_conversion_rate": 1,
|
||||
"order_type": "Sales",
|
||||
"customer": "_Test Customer",
|
||||
|
@ -239,6 +239,7 @@ def get_available_item_locations_for_batched_item(item_code, from_warehouses, re
|
||||
and sle.`item_code`=%(item_code)s
|
||||
and sle.`company` = %(company)s
|
||||
and batch.disabled = 0
|
||||
and sle.is_cancelled=0
|
||||
and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
|
||||
{warehouse_condition}
|
||||
GROUP BY
|
||||
|
@ -352,7 +352,7 @@ class PurchaseReceipt(BuyingController):
|
||||
if self.is_return or flt(d.item_tax_amount):
|
||||
loss_account = expenses_included_in_valuation
|
||||
else:
|
||||
loss_account = self.get_company_default("default_expense_account")
|
||||
loss_account = self.get_company_default("default_expense_account", ignore_validation=True) or stock_rbnb
|
||||
|
||||
cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center")
|
||||
|
||||
|
@ -23,9 +23,7 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
|
||||
def test_reverse_purchase_receipt_sle(self):
|
||||
|
||||
frappe.db.set_value('UOM', '_Test UOM', 'must_be_whole_number', 0)
|
||||
|
||||
pr = make_purchase_receipt(qty=0.5)
|
||||
pr = make_purchase_receipt(qty=0.5, item_code="_Test Item Home Desktop 200")
|
||||
|
||||
sl_entry = frappe.db.get_all("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
|
||||
"voucher_no": pr.name}, ['actual_qty'])
|
||||
@ -41,8 +39,6 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
self.assertEqual(len(sl_entry_cancelled), 2)
|
||||
self.assertEqual(sl_entry_cancelled[1].actual_qty, -0.5)
|
||||
|
||||
frappe.db.set_value('UOM', '_Test UOM', 'must_be_whole_number', 1)
|
||||
|
||||
def test_make_purchase_invoice(self):
|
||||
if not frappe.db.exists('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice'):
|
||||
frappe.get_doc({
|
||||
@ -336,10 +332,13 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
se3.cancel()
|
||||
po.reload()
|
||||
pr2.load_from_db()
|
||||
pr2.cancel()
|
||||
|
||||
po.load_from_db()
|
||||
po.cancel()
|
||||
if pr2.docstatus == 1 and frappe.db.get_value('Stock Ledger Entry',
|
||||
{'voucher_no': pr2.name, 'is_cancelled': 0}, 'name'):
|
||||
pr2.cancel()
|
||||
|
||||
po.load_from_db()
|
||||
po.cancel()
|
||||
|
||||
def test_serial_no_supplier(self):
|
||||
pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1)
|
||||
@ -1044,7 +1043,7 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
'account': srbnb_account,
|
||||
'voucher_detail_no': pr.items[1].name
|
||||
}, pluck="name")
|
||||
|
||||
|
||||
# check if the entries are not merged into one
|
||||
# seperate entries should be made since voucher_detail_no is different
|
||||
self.assertEqual(len(item_one_gl_entry), 1)
|
||||
@ -1055,13 +1054,13 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
def test_purchase_receipt_with_exchange_rate_difference(self):
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice as create_purchase_invoice
|
||||
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt as create_purchase_receipt
|
||||
|
||||
|
||||
pi = create_purchase_invoice(company="_Test Company with perpetual inventory",
|
||||
cost_center = "Main - TCP1",
|
||||
warehouse = "Stores - TCP1",
|
||||
expense_account ="_Test Account Cost for Goods Sold - TCP1",
|
||||
currency = "USD", conversion_rate = 70)
|
||||
|
||||
|
||||
pr = create_purchase_receipt(pi.name)
|
||||
pr.conversion_rate = 80
|
||||
pr.items[0].purchase_invoice = pi.name
|
||||
|
@ -29,13 +29,50 @@ frappe.ui.form.on('Repost Item Valuation', {
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
frm.trigger('setup_realtime_progress');
|
||||
},
|
||||
|
||||
setup_realtime_progress: function(frm) {
|
||||
frappe.realtime.on('item_reposting_progress', data => {
|
||||
if (frm.doc.name !== data.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (frm.doc.status == 'In Progress') {
|
||||
frm.doc.current_index = data.current_index;
|
||||
frm.doc.items_to_be_repost = data.items_to_be_repost;
|
||||
|
||||
frm.dashboard.reset();
|
||||
frm.trigger('show_reposting_progress');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.status == "Failed" && frm.doc.docstatus==1) {
|
||||
frm.add_custom_button(__('Restart'), function () {
|
||||
frm.trigger("restart_reposting");
|
||||
}).addClass("btn-primary");
|
||||
}
|
||||
|
||||
frm.trigger('show_reposting_progress');
|
||||
},
|
||||
|
||||
show_reposting_progress: function(frm) {
|
||||
var bars = [];
|
||||
|
||||
let total_count = frm.doc.items_to_be_repost ? JSON.parse(frm.doc.items_to_be_repost).length : 0;
|
||||
let progress = flt(cint(frm.doc.current_index) / total_count * 100, 2) || 0.5;
|
||||
var title = __('Reposting Completed {0}%', [progress]);
|
||||
|
||||
bars.push({
|
||||
'title': title,
|
||||
'width': progress + '%',
|
||||
'progress_class': 'progress-bar-success'
|
||||
});
|
||||
|
||||
frm.dashboard.add_progress(__('Reposting Progress'), bars);
|
||||
},
|
||||
|
||||
restart_reposting: function(frm) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user