Merge branch 'develop' into v12-lead-address-contact
@ -82,9 +82,7 @@
|
|||||||
},
|
},
|
||||||
"Inventory": {
|
"Inventory": {
|
||||||
"Consigned Stock": {
|
"Consigned Stock": {
|
||||||
"Handling Difference in Inventory": {
|
"Handling Difference in Inventory": {},
|
||||||
"account_type": "Stock Adjustment"
|
|
||||||
},
|
|
||||||
"Items Delivered to Customs on temporary Base": {}
|
"Items Delivered to Customs on temporary Base": {}
|
||||||
},
|
},
|
||||||
"Stock in Hand": {
|
"Stock in Hand": {
|
||||||
@ -190,6 +188,9 @@
|
|||||||
},
|
},
|
||||||
"Expenses Included In Valuation": {
|
"Expenses Included In Valuation": {
|
||||||
"account_type": "Expenses Included In Valuation"
|
"account_type": "Expenses Included In Valuation"
|
||||||
|
},
|
||||||
|
"Stock Adjustment": {
|
||||||
|
"account_type": "Stock Adjustment"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Depreciation": {
|
"Depreciation": {
|
||||||
|
|||||||
@ -185,7 +185,8 @@ def validate_account_types(accounts):
|
|||||||
return _("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing))
|
return _("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing))
|
||||||
|
|
||||||
account_types_for_group = ["Bank", "Cash", "Stock"]
|
account_types_for_group = ["Bank", "Cash", "Stock"]
|
||||||
account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] not in ('', 1)]
|
# fix logic bug
|
||||||
|
account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] == 1]
|
||||||
|
|
||||||
missing = list(set(account_types_for_group) - set(account_groups))
|
missing = list(set(account_types_for_group) - set(account_groups))
|
||||||
if missing:
|
if missing:
|
||||||
|
|||||||
@ -18,6 +18,7 @@ class CostCenter(NestedSet):
|
|||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_mandatory()
|
self.validate_mandatory()
|
||||||
|
self.validate_parent_cost_center()
|
||||||
|
|
||||||
def validate_mandatory(self):
|
def validate_mandatory(self):
|
||||||
if self.cost_center_name != self.company and not self.parent_cost_center:
|
if self.cost_center_name != self.company and not self.parent_cost_center:
|
||||||
@ -25,6 +26,12 @@ class CostCenter(NestedSet):
|
|||||||
elif self.cost_center_name == self.company and self.parent_cost_center:
|
elif self.cost_center_name == self.company and self.parent_cost_center:
|
||||||
frappe.throw(_("Root cannot have a parent cost center"))
|
frappe.throw(_("Root cannot have a parent cost center"))
|
||||||
|
|
||||||
|
def validate_parent_cost_center(self):
|
||||||
|
if self.parent_cost_center:
|
||||||
|
if not frappe.db.get_value('Cost Center', self.parent_cost_center, 'is_group'):
|
||||||
|
frappe.throw(_("{0} is not a group node. Please select a group node as parent cost center").format(
|
||||||
|
frappe.bold(self.parent_cost_center)))
|
||||||
|
|
||||||
def convert_group_to_ledger(self):
|
def convert_group_to_ledger(self):
|
||||||
if self.check_if_child_exists():
|
if self.check_if_child_exists():
|
||||||
frappe.throw(_("Cannot convert Cost Center to ledger as it has child nodes"))
|
frappe.throw(_("Cannot convert Cost Center to ledger as it has child nodes"))
|
||||||
|
|||||||
@ -1,12 +1,26 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
test_records = frappe.get_test_records('Cost Center')
|
test_records = frappe.get_test_records('Cost Center')
|
||||||
|
|
||||||
|
class TestCostCenter(unittest.TestCase):
|
||||||
|
def test_cost_center_creation_against_child_node(self):
|
||||||
|
|
||||||
|
if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center 2 - _TC'}):
|
||||||
|
frappe.get_doc(test_records[1]).insert()
|
||||||
|
|
||||||
|
cost_center = frappe.get_doc({
|
||||||
|
'doctype': 'Cost Center',
|
||||||
|
'cost_center_name': '_Test Cost Center 3',
|
||||||
|
'parent_cost_center': '_Test Cost Center 2 - _TC',
|
||||||
|
'is_group': 0,
|
||||||
|
'company': '_Test Company'
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, cost_center.save)
|
||||||
|
|
||||||
def create_cost_center(**args):
|
def create_cost_center(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
@ -350,13 +350,13 @@ def get_amount(ref_doc):
|
|||||||
if dt in ["Sales Order", "Purchase Order"]:
|
if dt in ["Sales Order", "Purchase Order"]:
|
||||||
grand_total = flt(ref_doc.grand_total) - flt(ref_doc.advance_paid)
|
grand_total = flt(ref_doc.grand_total) - flt(ref_doc.advance_paid)
|
||||||
|
|
||||||
if dt in ["Sales Invoice", "Purchase Invoice"]:
|
elif dt in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
if ref_doc.party_account_currency == ref_doc.currency:
|
if ref_doc.party_account_currency == ref_doc.currency:
|
||||||
grand_total = flt(ref_doc.outstanding_amount)
|
grand_total = flt(ref_doc.outstanding_amount)
|
||||||
else:
|
else:
|
||||||
grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
|
grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
|
||||||
|
|
||||||
if dt == "Fees":
|
elif dt == "Fees":
|
||||||
grand_total = ref_doc.outstanding_amount
|
grand_total = ref_doc.outstanding_amount
|
||||||
|
|
||||||
if grand_total > 0 :
|
if grand_total > 0 :
|
||||||
|
|||||||
@ -34,8 +34,7 @@ class PricingRule(Document):
|
|||||||
|
|
||||||
def validate_duplicate_apply_on(self):
|
def validate_duplicate_apply_on(self):
|
||||||
field = apply_on_dict.get(self.apply_on)
|
field = apply_on_dict.get(self.apply_on)
|
||||||
values = [d.get(frappe.scrub(self.apply_on)) for d in self.get(field)]
|
values = [d.get(frappe.scrub(self.apply_on)) for d in self.get(field) if field]
|
||||||
|
|
||||||
if len(values) != len(set(values)):
|
if len(values) != len(set(values)):
|
||||||
frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on))
|
frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on))
|
||||||
|
|
||||||
|
|||||||
@ -330,23 +330,6 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
frm: cur_frm
|
frm: cur_frm
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
item_code: function(frm, cdt, cdn) {
|
|
||||||
var row = locals[cdt][cdn];
|
|
||||||
if(row.item_code) {
|
|
||||||
frappe.call({
|
|
||||||
method: "erpnext.assets.doctype.asset_category.asset_category.get_asset_category_account",
|
|
||||||
args: {
|
|
||||||
"item": row.item_code,
|
|
||||||
"fieldname": "fixed_asset_account",
|
|
||||||
"company": frm.doc.company
|
|
||||||
},
|
|
||||||
callback: function(r, rt) {
|
|
||||||
frappe.model.set_value(cdt, cdn, "expense_account", r.message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cur_frm.script_manager.make(erpnext.accounts.PurchaseInvoice);
|
cur_frm.script_manager.make(erpnext.accounts.PurchaseInvoice);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"autoname": "hash",
|
"autoname": "hash",
|
||||||
"creation": "2013-05-22 12:43:10",
|
"creation": "2013-05-22 12:43:10",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@ -507,7 +508,8 @@
|
|||||||
"depends_on": "enable_deferred_expense",
|
"depends_on": "enable_deferred_expense",
|
||||||
"fieldname": "service_stop_date",
|
"fieldname": "service_stop_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Service Stop Date"
|
"label": "Service Stop Date",
|
||||||
|
"no_copy": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
@ -523,13 +525,15 @@
|
|||||||
"depends_on": "enable_deferred_expense",
|
"depends_on": "enable_deferred_expense",
|
||||||
"fieldname": "service_start_date",
|
"fieldname": "service_start_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Service Start Date"
|
"label": "Service Start Date",
|
||||||
|
"no_copy": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "enable_deferred_expense",
|
"depends_on": "enable_deferred_expense",
|
||||||
"fieldname": "service_end_date",
|
"fieldname": "service_end_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Service End Date"
|
"label": "Service End Date",
|
||||||
|
"no_copy": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "reference",
|
"fieldname": "reference",
|
||||||
@ -766,7 +770,8 @@
|
|||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2019-11-21 16:27:52.043744",
|
"links": [],
|
||||||
|
"modified": "2019-12-04 12:23:17.046413",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Item",
|
"name": "Purchase Invoice Item",
|
||||||
|
|||||||
@ -789,22 +789,21 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
method: "frappe.client.get_value",
|
method: "frappe.client.get_value",
|
||||||
args:{
|
args:{
|
||||||
doctype: "Patient",
|
doctype: "Patient",
|
||||||
filters: {"name": frm.doc.patient},
|
filters: {
|
||||||
|
"name": frm.doc.patient
|
||||||
|
},
|
||||||
fieldname: "customer"
|
fieldname: "customer"
|
||||||
},
|
},
|
||||||
callback:function(patient_customer) {
|
callback:function(r) {
|
||||||
if(patient_customer){
|
if(r && r.message.customer){
|
||||||
frm.set_value("customer", patient_customer.message.customer);
|
frm.set_value("customer", r.message.customer);
|
||||||
frm.refresh_fields();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else{
|
|
||||||
frm.set_value("customer", '');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
if (frappe.boot.active_domains.includes("Healthcare")){
|
if (frappe.boot.active_domains.includes("Healthcare")){
|
||||||
frm.set_df_property("patient", "hidden", 0);
|
frm.set_df_property("patient", "hidden", 0);
|
||||||
|
|||||||
@ -538,7 +538,7 @@ class SalesInvoice(SellingController):
|
|||||||
is_stock_item = frappe.get_cached_value('Item', d.item_code, 'is_stock_item')
|
is_stock_item = frappe.get_cached_value('Item', d.item_code, 'is_stock_item')
|
||||||
if (d.item_code and is_stock_item == 1\
|
if (d.item_code and is_stock_item == 1\
|
||||||
and not d.get(i.lower().replace(' ','_')) and not self.get(dic[i][1])):
|
and not d.get(i.lower().replace(' ','_')) and not self.get(dic[i][1])):
|
||||||
msgprint(_("{0} is mandatory for Item {1}").format(i,d.item_code), raise_exception=1)
|
msgprint(_("{0} is mandatory for Stock Item {1}").format(i,d.item_code), raise_exception=1)
|
||||||
|
|
||||||
|
|
||||||
def validate_proj_cust(self):
|
def validate_proj_cust(self):
|
||||||
@ -1048,13 +1048,18 @@ class SalesInvoice(SellingController):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
for serial_no in item.serial_no.split("\n"):
|
for serial_no in item.serial_no.split("\n"):
|
||||||
sales_invoice, item_code = frappe.db.get_value("Serial No", serial_no,
|
serial_no_details = frappe.db.get_value("Serial No", serial_no,
|
||||||
["sales_invoice", "item_code"])
|
["sales_invoice", "item_code"], as_dict=1)
|
||||||
if sales_invoice and item_code == item.item_code and self.name != sales_invoice:
|
|
||||||
sales_invoice_company = frappe.db.get_value("Sales Invoice", sales_invoice, "company")
|
if not serial_no_details:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if serial_no_details.sales_invoice and serial_no_details.item_code == item.item_code \
|
||||||
|
and self.name != serial_no_details.sales_invoice:
|
||||||
|
sales_invoice_company = frappe.db.get_value("Sales Invoice", serial_no_details.sales_invoice, "company")
|
||||||
if sales_invoice_company == self.company:
|
if sales_invoice_company == self.company:
|
||||||
frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}"
|
frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}"
|
||||||
.format(serial_no, sales_invoice)))
|
.format(serial_no, serial_no_details.sales_invoice)))
|
||||||
|
|
||||||
def update_project(self):
|
def update_project(self):
|
||||||
if self.project:
|
if self.project:
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"autoname": "hash",
|
"autoname": "hash",
|
||||||
"creation": "2013-06-04 11:02:19",
|
"creation": "2013-06-04 11:02:19",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@ -484,7 +485,8 @@
|
|||||||
"depends_on": "enable_deferred_revenue",
|
"depends_on": "enable_deferred_revenue",
|
||||||
"fieldname": "service_stop_date",
|
"fieldname": "service_stop_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Service Stop Date"
|
"label": "Service Stop Date",
|
||||||
|
"no_copy": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
@ -500,13 +502,15 @@
|
|||||||
"depends_on": "enable_deferred_revenue",
|
"depends_on": "enable_deferred_revenue",
|
||||||
"fieldname": "service_start_date",
|
"fieldname": "service_start_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Service Start Date"
|
"label": "Service Start Date",
|
||||||
|
"no_copy": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "enable_deferred_revenue",
|
"depends_on": "enable_deferred_revenue",
|
||||||
"fieldname": "service_end_date",
|
"fieldname": "service_end_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Service End Date"
|
"label": "Service End Date",
|
||||||
|
"no_copy": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
@ -783,7 +787,8 @@
|
|||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2019-07-16 16:36:46.527606",
|
"links": [],
|
||||||
|
"modified": "2019-12-04 12:22:38.517710",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Item",
|
"name": "Sales Invoice Item",
|
||||||
|
|||||||
@ -70,7 +70,7 @@ class ShippingRule(Document):
|
|||||||
|
|
||||||
def get_shipping_amount_from_rules(self, value):
|
def get_shipping_amount_from_rules(self, value):
|
||||||
for condition in self.get("conditions"):
|
for condition in self.get("conditions"):
|
||||||
if not condition.to_value or (flt(condition.from_value) <= value <= flt(condition.to_value)):
|
if not condition.to_value or (flt(condition.from_value) <= flt(value) <= flt(condition.to_value)):
|
||||||
return condition.shipping_amount
|
return condition.shipping_amount
|
||||||
|
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|||||||
@ -162,33 +162,34 @@ def validate_account_for_perpetual_inventory(gl_map):
|
|||||||
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
|
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
|
||||||
.format(account), StockAccountInvalidTransaction)
|
.format(account), StockAccountInvalidTransaction)
|
||||||
|
|
||||||
elif account_bal != stock_bal:
|
# This has been comment for a temporary, will add this code again on release of immutable ledger
|
||||||
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
|
# elif account_bal != stock_bal:
|
||||||
currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))
|
# precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
|
||||||
|
# currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))
|
||||||
|
|
||||||
diff = flt(stock_bal - account_bal, precision)
|
# diff = flt(stock_bal - account_bal, precision)
|
||||||
error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format(
|
# error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format(
|
||||||
stock_bal, account_bal, frappe.bold(account))
|
# stock_bal, account_bal, frappe.bold(account))
|
||||||
error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff))
|
# error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff))
|
||||||
stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account")
|
# stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account")
|
||||||
|
|
||||||
db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency')
|
# db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency')
|
||||||
db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency')
|
# db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency')
|
||||||
|
|
||||||
journal_entry_args = {
|
# journal_entry_args = {
|
||||||
'accounts':[
|
# 'accounts':[
|
||||||
{'account': account, db_or_cr_warehouse_account : abs(diff)},
|
# {'account': account, db_or_cr_warehouse_account : abs(diff)},
|
||||||
{'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }]
|
# {'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }]
|
||||||
}
|
# }
|
||||||
|
|
||||||
frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
|
# frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
|
||||||
raise_exception=StockValueAndAccountBalanceOutOfSync,
|
# raise_exception=StockValueAndAccountBalanceOutOfSync,
|
||||||
title=_('Values Out Of Sync'),
|
# title=_('Values Out Of Sync'),
|
||||||
primary_action={
|
# primary_action={
|
||||||
'label': _('Make Journal Entry'),
|
# 'label': _('Make Journal Entry'),
|
||||||
'client_action': 'erpnext.route_to_adjustment_jv',
|
# 'client_action': 'erpnext.route_to_adjustment_jv',
|
||||||
'args': journal_entry_args
|
# 'args': journal_entry_args
|
||||||
})
|
# })
|
||||||
|
|
||||||
def validate_cwip_accounts(gl_map):
|
def validate_cwip_accounts(gl_map):
|
||||||
cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")])
|
cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")])
|
||||||
|
|||||||
@ -100,6 +100,11 @@ frappe.query_reports["Accounts Payable"] = {
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Supplier Group"
|
"options": "Supplier Group"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"based_on_payment_terms",
|
||||||
|
"label": __("Based On Payment Terms"),
|
||||||
|
"fieldtype": "Check",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"tax_id",
|
"fieldname":"tax_id",
|
||||||
"label": __("Tax Id"),
|
"label": __("Tax Id"),
|
||||||
|
|||||||
@ -88,6 +88,11 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
|||||||
"label": __("Supplier Group"),
|
"label": __("Supplier Group"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Supplier Group"
|
"options": "Supplier Group"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"based_on_payment_terms",
|
||||||
|
"label": __("Based On Payment Terms"),
|
||||||
|
"fieldtype": "Check",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@ -60,6 +60,7 @@ class ReceivablePayableReport(object):
|
|||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
self.get_gl_entries()
|
self.get_gl_entries()
|
||||||
|
self.get_sales_invoices_or_customers_based_on_sales_person()
|
||||||
self.voucher_balance = OrderedDict()
|
self.voucher_balance = OrderedDict()
|
||||||
self.init_voucher_balance() # invoiced, paid, credit_note, outstanding
|
self.init_voucher_balance() # invoiced, paid, credit_note, outstanding
|
||||||
|
|
||||||
@ -103,12 +104,18 @@ class ReceivablePayableReport(object):
|
|||||||
|
|
||||||
def get_invoices(self, gle):
|
def get_invoices(self, gle):
|
||||||
if gle.voucher_type in ('Sales Invoice', 'Purchase Invoice'):
|
if gle.voucher_type in ('Sales Invoice', 'Purchase Invoice'):
|
||||||
self.invoices.add(gle.voucher_no)
|
if self.filters.get("sales_person"):
|
||||||
|
if gle.voucher_no in self.sales_person_records.get("Sales Invoice", []) \
|
||||||
|
or gle.party in self.sales_person_records.get("Customer", []):
|
||||||
|
self.invoices.add(gle.voucher_no)
|
||||||
|
else:
|
||||||
|
self.invoices.add(gle.voucher_no)
|
||||||
|
|
||||||
def update_voucher_balance(self, gle):
|
def update_voucher_balance(self, gle):
|
||||||
# get the row where this balance needs to be updated
|
# get the row where this balance needs to be updated
|
||||||
# if its a payment, it will return the linked invoice or will be considered as advance
|
# if its a payment, it will return the linked invoice or will be considered as advance
|
||||||
row = self.get_voucher_balance(gle)
|
row = self.get_voucher_balance(gle)
|
||||||
|
if not row: return
|
||||||
# gle_balance will be the total "debit - credit" for receivable type reports and
|
# gle_balance will be the total "debit - credit" for receivable type reports and
|
||||||
# and vice-versa for payable type reports
|
# and vice-versa for payable type reports
|
||||||
gle_balance = self.get_gle_balance(gle)
|
gle_balance = self.get_gle_balance(gle)
|
||||||
@ -129,8 +136,13 @@ class ReceivablePayableReport(object):
|
|||||||
row.paid -= gle_balance
|
row.paid -= gle_balance
|
||||||
|
|
||||||
def get_voucher_balance(self, gle):
|
def get_voucher_balance(self, gle):
|
||||||
voucher_balance = None
|
if self.filters.get("sales_person"):
|
||||||
|
against_voucher = gle.against_voucher or gle.voucher_no
|
||||||
|
if not (gle.party in self.sales_person_records.get("Customer", []) or \
|
||||||
|
against_voucher in self.sales_person_records.get("Sales Invoice", [])):
|
||||||
|
return
|
||||||
|
|
||||||
|
voucher_balance = None
|
||||||
if gle.against_voucher:
|
if gle.against_voucher:
|
||||||
# find invoice
|
# find invoice
|
||||||
against_voucher = gle.against_voucher
|
against_voucher = gle.against_voucher
|
||||||
@ -318,7 +330,7 @@ class ReceivablePayableReport(object):
|
|||||||
self.append_payment_term(row, d, term)
|
self.append_payment_term(row, d, term)
|
||||||
|
|
||||||
def append_payment_term(self, row, d, term):
|
def append_payment_term(self, row, d, term):
|
||||||
if self.filters.get("customer") and d.currency == d.party_account_currency:
|
if (self.filters.get("customer") or self.filters.get("supplier")) and d.currency == d.party_account_currency:
|
||||||
invoiced = d.payment_amount
|
invoiced = d.payment_amount
|
||||||
else:
|
else:
|
||||||
invoiced = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision)
|
invoiced = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision)
|
||||||
@ -512,6 +524,22 @@ class ReceivablePayableReport(object):
|
|||||||
order by posting_date, party"""
|
order by posting_date, party"""
|
||||||
.format(select_fields, conditions), values, as_dict=True)
|
.format(select_fields, conditions), values, as_dict=True)
|
||||||
|
|
||||||
|
def get_sales_invoices_or_customers_based_on_sales_person(self):
|
||||||
|
if self.filters.get("sales_person"):
|
||||||
|
lft, rgt = frappe.db.get_value("Sales Person",
|
||||||
|
self.filters.get("sales_person"), ["lft", "rgt"])
|
||||||
|
|
||||||
|
records = frappe.db.sql("""
|
||||||
|
select distinct parent, parenttype
|
||||||
|
from `tabSales Team` steam
|
||||||
|
where parenttype in ('Customer', 'Sales Invoice')
|
||||||
|
and exists(select name from `tabSales Person` where lft >= %s and rgt <= %s and name = steam.sales_person)
|
||||||
|
""", (lft, rgt), as_dict=1)
|
||||||
|
|
||||||
|
self.sales_person_records = frappe._dict()
|
||||||
|
for d in records:
|
||||||
|
self.sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
|
||||||
|
|
||||||
def prepare_conditions(self):
|
def prepare_conditions(self):
|
||||||
conditions = [""]
|
conditions = [""]
|
||||||
values = [self.party_type, self.filters.report_date]
|
values = [self.party_type, self.filters.report_date]
|
||||||
@ -564,16 +592,6 @@ class ReceivablePayableReport(object):
|
|||||||
conditions.append("party in (select name from tabCustomer where default_sales_partner=%s)")
|
conditions.append("party in (select name from tabCustomer where default_sales_partner=%s)")
|
||||||
values.append(self.filters.get("sales_partner"))
|
values.append(self.filters.get("sales_partner"))
|
||||||
|
|
||||||
if self.filters.get("sales_person"):
|
|
||||||
lft, rgt = frappe.db.get_value("Sales Person",
|
|
||||||
self.filters.get("sales_person"), ["lft", "rgt"])
|
|
||||||
|
|
||||||
conditions.append("""exists(select name from `tabSales Team` steam where
|
|
||||||
steam.sales_person in (select name from `tabSales Person` where lft >= {0} and rgt <= {1})
|
|
||||||
and ((steam.parent = voucher_no and steam.parenttype = voucher_type)
|
|
||||||
or (steam.parent = against_voucher and steam.parenttype = against_voucher_type)
|
|
||||||
or (steam.parent = party and steam.parenttype = 'Customer')))""".format(lft, rgt))
|
|
||||||
|
|
||||||
def add_supplier_filters(self, conditions, values):
|
def add_supplier_filters(self, conditions, values):
|
||||||
if self.filters.get("supplier_group"):
|
if self.filters.get("supplier_group"):
|
||||||
conditions.append("""party in (select name from tabSupplier
|
conditions.append("""party in (select name from tabSupplier
|
||||||
|
|||||||
@ -106,6 +106,11 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
|||||||
"label": __("Sales Person"),
|
"label": __("Sales Person"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Sales Person"
|
"options": "Sales Person"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"based_on_payment_terms",
|
||||||
|
"label": __("Based On Payment Terms"),
|
||||||
|
"fieldtype": "Check",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
|||||||
self.filters.report_date) or {}
|
self.filters.report_date) or {}
|
||||||
|
|
||||||
for party, party_dict in iteritems(self.party_total):
|
for party, party_dict in iteritems(self.party_total):
|
||||||
if party_dict.outstanding <= 0:
|
if party_dict.outstanding == 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
row = frappe._dict()
|
row = frappe._dict()
|
||||||
|
|||||||
@ -18,11 +18,14 @@ def execute(filters=None):
|
|||||||
return columns, data
|
return columns, data
|
||||||
|
|
||||||
def get_data(filters, show_party_name):
|
def get_data(filters, show_party_name):
|
||||||
party_name_field = "{0}_name".format(frappe.scrub(filters.get('party_type')))
|
if filters.get('party_type') in ('Customer', 'Supplier', 'Employee', 'Member'):
|
||||||
|
party_name_field = "{0}_name".format(frappe.scrub(filters.get('party_type')))
|
||||||
if filters.get('party_type') == 'Student':
|
if filters.get('party_type') == 'Student':
|
||||||
party_name_field = 'first_name'
|
party_name_field = 'first_name'
|
||||||
elif filters.get('party_type') == 'Shareholder':
|
elif filters.get('party_type') == 'Shareholder':
|
||||||
party_name_field = 'title'
|
party_name_field = 'title'
|
||||||
|
else:
|
||||||
|
party_name_field = 'name'
|
||||||
|
|
||||||
party_filters = {"name": filters.get("party")} if filters.get("party") else {}
|
party_filters = {"name": filters.get("party")} if filters.get("party") else {}
|
||||||
parties = frappe.get_all(filters.get("party_type"), fields = ["name", party_name_field],
|
parties = frappe.get_all(filters.get("party_type"), fields = ["name", party_name_field],
|
||||||
|
|||||||
@ -569,7 +569,7 @@ def get_stock_and_account_balance(account=None, posting_date=None, company=None)
|
|||||||
|
|
||||||
warehouse_account = get_warehouse_account_map(company)
|
warehouse_account = get_warehouse_account_map(company)
|
||||||
|
|
||||||
account_balance = get_balance_on(account, posting_date, in_account_currency=False)
|
account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True)
|
||||||
|
|
||||||
related_warehouses = [wh for wh, wh_details in warehouse_account.items()
|
related_warehouses = [wh for wh, wh_details in warehouse_account.items()
|
||||||
if wh_details.account == account and not wh_details.is_group]
|
if wh_details.account == account and not wh_details.is_group]
|
||||||
|
|||||||
@ -517,15 +517,18 @@ def update_maintenance_status():
|
|||||||
asset.set_status('Out of Order')
|
asset.set_status('Out of Order')
|
||||||
|
|
||||||
def make_post_gl_entry():
|
def make_post_gl_entry():
|
||||||
if not is_cwip_accounting_enabled(self.asset_category):
|
|
||||||
return
|
|
||||||
|
|
||||||
assets = frappe.db.sql_list(""" select name from `tabAsset`
|
asset_categories = frappe.db.get_all('Asset Category', fields = ['name', 'enable_cwip_accounting'])
|
||||||
where ifnull(booked_fixed_asset, 0) = 0 and available_for_use_date = %s""", nowdate())
|
|
||||||
|
|
||||||
for asset in assets:
|
for asset_category in asset_categories:
|
||||||
doc = frappe.get_doc('Asset', asset)
|
if cint(asset_category.enable_cwip_accounting):
|
||||||
doc.make_gl_entries()
|
assets = frappe.db.sql_list(""" select name from `tabAsset`
|
||||||
|
where asset_category = %s and ifnull(booked_fixed_asset, 0) = 0
|
||||||
|
and available_for_use_date = %s""", (asset_category.name, nowdate()))
|
||||||
|
|
||||||
|
for asset in assets:
|
||||||
|
doc = frappe.get_doc('Asset', asset)
|
||||||
|
doc.make_gl_entries()
|
||||||
|
|
||||||
def get_asset_naming_series():
|
def get_asset_naming_series():
|
||||||
meta = frappe.get_meta('Asset')
|
meta = frappe.get_meta('Asset')
|
||||||
|
|||||||
@ -29,7 +29,8 @@ def get_asset_category_account(fieldname, item=None, asset=None, account=None, a
|
|||||||
account=None
|
account=None
|
||||||
|
|
||||||
if not account:
|
if not account:
|
||||||
asset_category, company = frappe.db.get_value("Asset", asset, ["asset_category", "company"])
|
asset_details = frappe.db.get_value("Asset", asset, ["asset_category", "company"])
|
||||||
|
asset_category, company = asset_details or [None, None]
|
||||||
|
|
||||||
account = frappe.db.get_value("Asset Category Account",
|
account = frappe.db.get_value("Asset Category Account",
|
||||||
filters={"parent": asset_category, "company_name": company}, fieldname=fieldname)
|
filters={"parent": asset_category, "company_name": company}, fieldname=fieldname)
|
||||||
|
|||||||
@ -26,5 +26,11 @@ frappe.query_reports["Fixed Asset Register"] = {
|
|||||||
fieldtype: "Link",
|
fieldtype: "Link",
|
||||||
options: "Finance Book"
|
options: "Finance Book"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldname:"date",
|
||||||
|
label: __("Date"),
|
||||||
|
fieldtype: "Date",
|
||||||
|
default: frappe.datetime.get_today()
|
||||||
|
},
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cstr
|
from frappe.utils import cstr, today, flt
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
filters = frappe._dict(filters or {})
|
filters = frappe._dict(filters or {})
|
||||||
@ -86,8 +86,8 @@ def get_columns(filters):
|
|||||||
"width": 90
|
"width": 90
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Current Value"),
|
"label": _("Asset Value"),
|
||||||
"fieldname": "current_value",
|
"fieldname": "asset_value",
|
||||||
"options": "Currency",
|
"options": "Currency",
|
||||||
"width": 90
|
"width": 90
|
||||||
},
|
},
|
||||||
@ -114,7 +114,7 @@ def get_data(filters):
|
|||||||
data = []
|
data = []
|
||||||
|
|
||||||
conditions = get_conditions(filters)
|
conditions = get_conditions(filters)
|
||||||
current_value_map = get_finance_book_value_map(filters.finance_book)
|
depreciation_amount_map = get_finance_book_value_map(filters.date, filters.finance_book)
|
||||||
pr_supplier_map = get_purchase_receipt_supplier_map()
|
pr_supplier_map = get_purchase_receipt_supplier_map()
|
||||||
pi_supplier_map = get_purchase_invoice_supplier_map()
|
pi_supplier_map = get_purchase_invoice_supplier_map()
|
||||||
|
|
||||||
@ -125,7 +125,9 @@ def get_data(filters):
|
|||||||
"available_for_use_date", "status", "purchase_invoice"])
|
"available_for_use_date", "status", "purchase_invoice"])
|
||||||
|
|
||||||
for asset in assets_record:
|
for asset in assets_record:
|
||||||
if current_value_map.get(asset.name) is not None:
|
asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \
|
||||||
|
- flt(depreciation_amount_map.get(asset.name))
|
||||||
|
if asset_value:
|
||||||
row = {
|
row = {
|
||||||
"asset_id": asset.name,
|
"asset_id": asset.name,
|
||||||
"asset_name": asset.asset_name,
|
"asset_name": asset.asset_name,
|
||||||
@ -138,19 +140,24 @@ def get_data(filters):
|
|||||||
"location": asset.location,
|
"location": asset.location,
|
||||||
"asset_category": asset.asset_category,
|
"asset_category": asset.asset_category,
|
||||||
"purchase_date": asset.purchase_date,
|
"purchase_date": asset.purchase_date,
|
||||||
"current_value": current_value_map.get(asset.name)
|
"asset_value": asset_value
|
||||||
}
|
}
|
||||||
data.append(row)
|
data.append(row)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_finance_book_value_map(finance_book=''):
|
def get_finance_book_value_map(date, finance_book=''):
|
||||||
|
if not date:
|
||||||
|
date = today()
|
||||||
return frappe._dict(frappe.db.sql(''' Select
|
return frappe._dict(frappe.db.sql(''' Select
|
||||||
parent, value_after_depreciation
|
parent, SUM(depreciation_amount)
|
||||||
FROM `tabAsset Finance Book`
|
FROM `tabDepreciation Schedule`
|
||||||
WHERE
|
WHERE
|
||||||
parentfield='finance_books'
|
parentfield='schedules'
|
||||||
AND ifnull(finance_book, '')=%s''', cstr(finance_book)))
|
AND schedule_date<=%s
|
||||||
|
AND journal_entry IS NOT NULL
|
||||||
|
AND ifnull(finance_book, '')=%s
|
||||||
|
GROUP BY parent''', (date, cstr(finance_book))))
|
||||||
|
|
||||||
def get_purchase_receipt_supplier_map():
|
def get_purchase_receipt_supplier_map():
|
||||||
return frappe._dict(frappe.db.sql(''' Select
|
return frappe._dict(frappe.db.sql(''' Select
|
||||||
|
|||||||
@ -18,6 +18,7 @@ frappe.ui.form.on("Purchase Order", {
|
|||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
"company": frm.doc.company,
|
"company": frm.doc.company,
|
||||||
|
"name": ['!=', frm.doc.supplier_warehouse],
|
||||||
"is_group": 0
|
"is_group": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -283,6 +284,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
me.dialog.get_field('sub_con_rm_items').check_all_rows()
|
||||||
|
|
||||||
me.dialog.show()
|
me.dialog.show()
|
||||||
this.dialog.set_primary_action(__('Transfer'), function() {
|
this.dialog.set_primary_action(__('Transfer'), function() {
|
||||||
me.values = me.dialog.get_values();
|
me.values = me.dialog.get_values();
|
||||||
|
|||||||
@ -17,6 +17,8 @@ from erpnext.stock.doctype.material_request.material_request import make_purchas
|
|||||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
from erpnext.controllers.accounts_controller import update_child_qty_rate
|
from erpnext.controllers.accounts_controller import update_child_qty_rate
|
||||||
from erpnext.controllers.status_updater import OverAllowanceError
|
from erpnext.controllers.status_updater import OverAllowanceError
|
||||||
|
from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order
|
||||||
|
|
||||||
|
|
||||||
class TestPurchaseOrder(unittest.TestCase):
|
class TestPurchaseOrder(unittest.TestCase):
|
||||||
def test_make_purchase_receipt(self):
|
def test_make_purchase_receipt(self):
|
||||||
@ -519,47 +521,62 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
def test_backflush_based_on_stock_entry(self):
|
def test_backflush_based_on_stock_entry(self):
|
||||||
item_code = "_Test Subcontracted FG Item 1"
|
item_code = "_Test Subcontracted FG Item 1"
|
||||||
make_subcontracted_item(item_code)
|
make_subcontracted_item(item_code)
|
||||||
|
make_item('Sub Contracted Raw Material 1', {
|
||||||
|
'is_stock_item': 1,
|
||||||
|
'is_sub_contracted_item': 1
|
||||||
|
})
|
||||||
|
|
||||||
update_backflush_based_on("Material Transferred for Subcontract")
|
update_backflush_based_on("Material Transferred for Subcontract")
|
||||||
po = create_purchase_order(item_code=item_code, qty=1,
|
|
||||||
|
order_qty = 5
|
||||||
|
po = create_purchase_order(item_code=item_code, qty=order_qty,
|
||||||
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
|
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
|
||||||
|
|
||||||
make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100)
|
|
||||||
make_stock_entry(target="_Test Warehouse - _TC",
|
make_stock_entry(target="_Test Warehouse - _TC",
|
||||||
item_code="_Test Item Home Desktop 100", qty=10, basic_rate=100)
|
item_code="_Test Item Home Desktop 100", qty=10, basic_rate=100)
|
||||||
make_stock_entry(target="_Test Warehouse - _TC",
|
make_stock_entry(target="_Test Warehouse - _TC",
|
||||||
item_code = "Test Extra Item 1", qty=100, basic_rate=100)
|
item_code = "Test Extra Item 1", qty=100, basic_rate=100)
|
||||||
make_stock_entry(target="_Test Warehouse - _TC",
|
make_stock_entry(target="_Test Warehouse - _TC",
|
||||||
item_code = "Test Extra Item 2", qty=10, basic_rate=100)
|
item_code = "Test Extra Item 2", qty=10, basic_rate=100)
|
||||||
|
make_stock_entry(target="_Test Warehouse - _TC",
|
||||||
|
item_code = "Sub Contracted Raw Material 1", qty=10, basic_rate=100)
|
||||||
|
|
||||||
rm_item = [
|
rm_items = [
|
||||||
{"item_code":item_code,"rm_item_code":"_Test Item","item_name":"_Test Item",
|
{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 1","item_name":"_Test Item",
|
||||||
"qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":100,"stock_uom":"Nos"},
|
"qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
|
||||||
{"item_code":item_code,"rm_item_code":"_Test Item Home Desktop 100","item_name":"_Test Item Home Desktop 100",
|
{"item_code":item_code,"rm_item_code":"_Test Item Home Desktop 100","item_name":"_Test Item Home Desktop 100",
|
||||||
"qty":2,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"},
|
"qty":20,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
|
||||||
{"item_code":item_code,"rm_item_code":"Test Extra Item 1","item_name":"Test Extra Item 1",
|
{"item_code":item_code,"rm_item_code":"Test Extra Item 1","item_name":"Test Extra Item 1",
|
||||||
"qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"}]
|
"qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
|
||||||
|
{'item_code': item_code, 'rm_item_code': 'Test Extra Item 2', 'stock_uom':'Nos',
|
||||||
|
'qty': 10, 'warehouse': '_Test Warehouse - _TC', 'item_name':'Test Extra Item 2'}]
|
||||||
|
|
||||||
rm_item_string = json.dumps(rm_item)
|
rm_item_string = json.dumps(rm_items)
|
||||||
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
|
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
|
||||||
se.append('items', {
|
|
||||||
'item_code': "Test Extra Item 2",
|
|
||||||
"qty": 1,
|
|
||||||
"rate": 100,
|
|
||||||
"s_warehouse": "_Test Warehouse - _TC",
|
|
||||||
"t_warehouse": "_Test Warehouse 1 - _TC"
|
|
||||||
})
|
|
||||||
se.set_missing_values()
|
|
||||||
se.submit()
|
se.submit()
|
||||||
|
|
||||||
pr = make_purchase_receipt(po.name)
|
pr = make_purchase_receipt(po.name)
|
||||||
|
|
||||||
|
received_qty = 2
|
||||||
|
# partial receipt
|
||||||
|
pr.get('items')[0].qty = received_qty
|
||||||
pr.save()
|
pr.save()
|
||||||
pr.submit()
|
pr.submit()
|
||||||
|
|
||||||
se_items = sorted([d.item_code for d in se.get('items')])
|
transferred_items = sorted([d.item_code for d in se.get('items') if se.purchase_order == po.name])
|
||||||
supplied_items = sorted([d.rm_item_code for d in pr.get('supplied_items')])
|
issued_items = sorted([d.rm_item_code for d in pr.get('supplied_items')])
|
||||||
|
|
||||||
|
self.assertEquals(transferred_items, issued_items)
|
||||||
|
self.assertEquals(pr.get('items')[0].rm_supp_cost, 2000)
|
||||||
|
|
||||||
|
|
||||||
|
transferred_rm_map = frappe._dict()
|
||||||
|
for item in rm_items:
|
||||||
|
transferred_rm_map[item.get('rm_item_code')] = item
|
||||||
|
|
||||||
|
for item in pr.get('supplied_items'):
|
||||||
|
self.assertEqual(item.get('required_qty'), (transferred_rm_map[item.get('rm_item_code')].get('qty') / order_qty) * received_qty)
|
||||||
|
|
||||||
self.assertEquals(se_items, supplied_items)
|
|
||||||
update_backflush_based_on("BOM")
|
update_backflush_based_on("BOM")
|
||||||
|
|
||||||
def test_advance_payment_entry_unlink_against_purchase_order(self):
|
def test_advance_payment_entry_unlink_against_purchase_order(self):
|
||||||
@ -606,6 +623,27 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
self.assertEqual(po.schedule_date, add_days(nowdate(), 2))
|
self.assertEqual(po.schedule_date, add_days(nowdate(), 2))
|
||||||
|
|
||||||
|
|
||||||
|
def test_po_optional_blanket_order(self):
|
||||||
|
"""
|
||||||
|
Expected result: Blanket order Ordered Quantity should only be affected on Purchase Order with against_blanket_order = 1.
|
||||||
|
Second Purchase Order should not add on to Blanket Orders Ordered Quantity.
|
||||||
|
"""
|
||||||
|
|
||||||
|
bo = make_blanket_order(blanket_order_type = "Purchasing", quantity = 10, rate = 10)
|
||||||
|
|
||||||
|
po = create_purchase_order(item_code= "_Test Item", qty = 5, against_blanket_order = 1)
|
||||||
|
po_doc = frappe.get_doc('Purchase Order', po.get('name'))
|
||||||
|
# To test if the PO has a Blanket Order
|
||||||
|
self.assertTrue(po_doc.items[0].blanket_order)
|
||||||
|
|
||||||
|
po = create_purchase_order(item_code= "_Test Item", qty = 5, against_blanket_order = 0)
|
||||||
|
po_doc = frappe.get_doc('Purchase Order', po.get('name'))
|
||||||
|
# To test if the PO does NOT have a Blanket Order
|
||||||
|
self.assertEqual(po_doc.items[0].blanket_order, None)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def make_pr_against_po(po, received_qty=0):
|
def make_pr_against_po(po, received_qty=0):
|
||||||
pr = make_purchase_receipt(po)
|
pr = make_purchase_receipt(po)
|
||||||
pr.get("items")[0].qty = received_qty or 5
|
pr.get("items")[0].qty = received_qty or 5
|
||||||
@ -678,7 +716,8 @@ def create_purchase_order(**args):
|
|||||||
"qty": args.qty or 10,
|
"qty": args.qty or 10,
|
||||||
"rate": args.rate or 500,
|
"rate": args.rate or 500,
|
||||||
"schedule_date": add_days(nowdate(), 1),
|
"schedule_date": add_days(nowdate(), 1),
|
||||||
"include_exploded_items": args.get('include_exploded_items', 1)
|
"include_exploded_items": args.get('include_exploded_items', 1),
|
||||||
|
"against_blanket_order": args.against_blanket_order
|
||||||
})
|
})
|
||||||
if not args.do_not_save:
|
if not args.do_not_save:
|
||||||
po.insert()
|
po.insert()
|
||||||
|
|||||||
@ -43,7 +43,6 @@
|
|||||||
"base_amount",
|
"base_amount",
|
||||||
"pricing_rules",
|
"pricing_rules",
|
||||||
"is_free_item",
|
"is_free_item",
|
||||||
"is_fixed_asset",
|
|
||||||
"section_break_29",
|
"section_break_29",
|
||||||
"net_rate",
|
"net_rate",
|
||||||
"net_amount",
|
"net_amount",
|
||||||
@ -67,6 +66,7 @@
|
|||||||
"supplier_quotation",
|
"supplier_quotation",
|
||||||
"supplier_quotation_item",
|
"supplier_quotation_item",
|
||||||
"col_break5",
|
"col_break5",
|
||||||
|
"against_blanket_order",
|
||||||
"blanket_order",
|
"blanket_order",
|
||||||
"blanket_order_rate",
|
"blanket_order_rate",
|
||||||
"item_group",
|
"item_group",
|
||||||
@ -511,6 +511,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:doc.against_blanket_order",
|
||||||
"fieldname": "blanket_order",
|
"fieldname": "blanket_order",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Blanket Order",
|
"label": "Blanket Order",
|
||||||
@ -518,6 +519,7 @@
|
|||||||
"options": "Blanket Order"
|
"options": "Blanket Order"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:doc.against_blanket_order",
|
||||||
"fieldname": "blanket_order_rate",
|
"fieldname": "blanket_order_rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Blanket Order Rate",
|
"label": "Blanket Order Rate",
|
||||||
@ -703,16 +705,14 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fetch_from": "item_code.is_fixed_asset",
|
"fieldname": "against_blanket_order",
|
||||||
"fieldname": "is_fixed_asset",
|
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Is Fixed Asset",
|
"label": "Against Blanket Order"
|
||||||
"read_only": 1
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2019-11-07 17:19:12.090355",
|
"modified": "2019-11-19 14:10:52.865006",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order Item",
|
"name": "Purchase Order Item",
|
||||||
|
|||||||
@ -56,3 +56,23 @@ class Supplier(TransactionBase):
|
|||||||
def after_rename(self, olddn, newdn, merge=False):
|
def after_rename(self, olddn, newdn, merge=False):
|
||||||
if frappe.defaults.get_global_default('supp_master_name') == 'Supplier Name':
|
if frappe.defaults.get_global_default('supp_master_name') == 'Supplier Name':
|
||||||
frappe.db.set(self, "supplier_name", newdn)
|
frappe.db.set(self, "supplier_name", newdn)
|
||||||
|
|
||||||
|
def create_onboarding_docs(self, args):
|
||||||
|
defaults = frappe.defaults.get_defaults()
|
||||||
|
for i in range(1, args.get('max_count')):
|
||||||
|
supplier = args.get('supplier_name_' + str(i))
|
||||||
|
if supplier:
|
||||||
|
try:
|
||||||
|
doc = frappe.get_doc({
|
||||||
|
'doctype': self.doctype,
|
||||||
|
'supplier_name': supplier,
|
||||||
|
'supplier_group': _('Local'),
|
||||||
|
'company': defaults.get('company')
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
if args.get('supplier_email_' + str(i)):
|
||||||
|
from erpnext.selling.doctype.customer.customer import create_contact
|
||||||
|
create_contact(supplier, 'Supplier',
|
||||||
|
doc.name, args.get('supplier_email_' + str(i)))
|
||||||
|
except frappe.NameError:
|
||||||
|
pass
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"add_more_button": 1,
|
||||||
|
"app": "ERPNext",
|
||||||
|
"creation": "2019-11-15 14:45:32.626641",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Setup Wizard Slide",
|
||||||
|
"domains": [],
|
||||||
|
"help_links": [
|
||||||
|
{
|
||||||
|
"label": "Supplier",
|
||||||
|
"video_id": "zsrrVDk6VBs"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"idx": 0,
|
||||||
|
"image_src": "/assets/erpnext/images/illustrations/supplier.png",
|
||||||
|
"max_count": 3,
|
||||||
|
"modified": "2019-11-26 18:26:25.498325",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"name": "Add A Few Suppliers",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"ref_doctype": "Supplier",
|
||||||
|
"slide_desc": "",
|
||||||
|
"slide_fields": [
|
||||||
|
{
|
||||||
|
"align": "",
|
||||||
|
"fieldname": "supplier_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Supplier Name",
|
||||||
|
"placeholder": "",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"align": "",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"reqd": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"align": "",
|
||||||
|
"fieldname": "supplier_email",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Supplier Email",
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"slide_order": 50,
|
||||||
|
"slide_title": "Add A Few Suppliers",
|
||||||
|
"slide_type": "Create",
|
||||||
|
"submit_method": ""
|
||||||
|
}
|
||||||
@ -197,6 +197,11 @@ def get_data():
|
|||||||
"name": "Bank Reconciliation Statement",
|
"name": "Bank Reconciliation Statement",
|
||||||
"is_query_report": True,
|
"is_query_report": True,
|
||||||
"doctype": "Journal Entry"
|
"doctype": "Journal Entry"
|
||||||
|
},{
|
||||||
|
"type": "page",
|
||||||
|
"name": "bank-reconciliation",
|
||||||
|
"label": _("Bank Reconciliation"),
|
||||||
|
"icon": "fa fa-bar-chart"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "report",
|
"type": "report",
|
||||||
|
|||||||
@ -241,6 +241,10 @@ def get_data():
|
|||||||
"type": "doctype",
|
"type": "doctype",
|
||||||
"name": "Quality Inspection Template",
|
"name": "Quality Inspection Template",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Quick Stock Balance",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -61,7 +61,6 @@ class AccountsController(TransactionBase):
|
|||||||
_('{0} is blocked so this transaction cannot proceed'.format(supplier_name)), raise_exception=1)
|
_('{0} is blocked so this transaction cannot proceed'.format(supplier_name)), raise_exception=1)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
|
||||||
if not self.get('is_return'):
|
if not self.get('is_return'):
|
||||||
self.validate_qty_is_not_zero()
|
self.validate_qty_is_not_zero()
|
||||||
|
|
||||||
@ -100,11 +99,23 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
if self.is_return:
|
if self.is_return:
|
||||||
self.validate_qty()
|
self.validate_qty()
|
||||||
|
else:
|
||||||
|
self.validate_deferred_start_and_end_date()
|
||||||
|
|
||||||
validate_regional(self)
|
validate_regional(self)
|
||||||
if self.doctype != 'Material Request':
|
if self.doctype != 'Material Request':
|
||||||
apply_pricing_rule_on_transaction(self)
|
apply_pricing_rule_on_transaction(self)
|
||||||
|
|
||||||
|
def validate_deferred_start_and_end_date(self):
|
||||||
|
for d in self.items:
|
||||||
|
if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):
|
||||||
|
if not (d.service_start_date and d.service_end_date):
|
||||||
|
frappe.throw(_("Row #{0}: Service Start and End Date is required for deferred accounting").format(d.idx))
|
||||||
|
elif getdate(d.service_start_date) > getdate(d.service_end_date):
|
||||||
|
frappe.throw(_("Row #{0}: Service Start Date cannot be greater than Service End Date").format(d.idx))
|
||||||
|
elif getdate(self.posting_date) > getdate(d.service_end_date):
|
||||||
|
frappe.throw(_("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx))
|
||||||
|
|
||||||
def validate_invoice_documents_schedule(self):
|
def validate_invoice_documents_schedule(self):
|
||||||
self.validate_payment_schedule_dates()
|
self.validate_payment_schedule_dates()
|
||||||
self.set_due_date()
|
self.set_due_date()
|
||||||
@ -415,9 +426,10 @@ class AccountsController(TransactionBase):
|
|||||||
return gl_dict
|
return gl_dict
|
||||||
|
|
||||||
def validate_qty_is_not_zero(self):
|
def validate_qty_is_not_zero(self):
|
||||||
for item in self.items:
|
if self.doctype != "Purchase Receipt":
|
||||||
if not item.qty:
|
for item in self.items:
|
||||||
frappe.throw(_("Item quantity can not be zero"))
|
if not item.qty:
|
||||||
|
frappe.throw(_("Item quantity can not be zero"))
|
||||||
|
|
||||||
def validate_account_currency(self, account, account_currency=None):
|
def validate_account_currency(self, account, account_currency=None):
|
||||||
valid_currency = [self.company_currency]
|
valid_currency = [self.company_currency]
|
||||||
|
|||||||
@ -221,7 +221,7 @@ class BuyingController(StockController):
|
|||||||
"backflush_raw_materials_of_subcontract_based_on")
|
"backflush_raw_materials_of_subcontract_based_on")
|
||||||
if (self.doctype == 'Purchase Receipt' and
|
if (self.doctype == 'Purchase Receipt' and
|
||||||
backflush_raw_materials_based_on != 'BOM'):
|
backflush_raw_materials_based_on != 'BOM'):
|
||||||
self.update_raw_materials_supplied_based_on_stock_entries(raw_material_table)
|
self.update_raw_materials_supplied_based_on_stock_entries()
|
||||||
else:
|
else:
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||||
@ -241,41 +241,95 @@ class BuyingController(StockController):
|
|||||||
if self.is_subcontracted == "No" and self.get("supplied_items"):
|
if self.is_subcontracted == "No" and self.get("supplied_items"):
|
||||||
self.set('supplied_items', [])
|
self.set('supplied_items', [])
|
||||||
|
|
||||||
def update_raw_materials_supplied_based_on_stock_entries(self, raw_material_table):
|
def update_raw_materials_supplied_based_on_stock_entries(self):
|
||||||
self.set(raw_material_table, [])
|
self.set('supplied_items', [])
|
||||||
purchase_orders = [d.purchase_order for d in self.items]
|
|
||||||
if purchase_orders:
|
|
||||||
items = get_subcontracted_raw_materials_from_se(purchase_orders)
|
|
||||||
backflushed_raw_materials = get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, self.name)
|
|
||||||
|
|
||||||
for d in items:
|
purchase_orders = set([d.purchase_order for d in self.items])
|
||||||
qty = d.qty - backflushed_raw_materials.get(d.item_code, 0)
|
|
||||||
rm = self.append(raw_material_table, {})
|
|
||||||
rm.rm_item_code = d.item_code
|
|
||||||
rm.item_name = d.item_name
|
|
||||||
rm.main_item_code = d.main_item_code
|
|
||||||
rm.description = d.description
|
|
||||||
rm.stock_uom = d.stock_uom
|
|
||||||
rm.required_qty = qty
|
|
||||||
rm.consumed_qty = qty
|
|
||||||
rm.serial_no = d.serial_no
|
|
||||||
rm.batch_no = d.batch_no
|
|
||||||
|
|
||||||
# get raw materials rate
|
# qty of raw materials backflushed (for each item per purchase order)
|
||||||
from erpnext.stock.utils import get_incoming_rate
|
backflushed_raw_materials_map = get_backflushed_subcontracted_raw_materials(purchase_orders)
|
||||||
rm.rate = get_incoming_rate({
|
|
||||||
"item_code": d.item_code,
|
|
||||||
"warehouse": self.supplier_warehouse,
|
|
||||||
"posting_date": self.posting_date,
|
|
||||||
"posting_time": self.posting_time,
|
|
||||||
"qty": -1 * qty,
|
|
||||||
"serial_no": rm.serial_no
|
|
||||||
})
|
|
||||||
if not rm.rate:
|
|
||||||
rm.rate = get_valuation_rate(d.item_code, self.supplier_warehouse,
|
|
||||||
self.doctype, self.name, currency=self.company_currency, company = self.company)
|
|
||||||
|
|
||||||
rm.amount = qty * flt(rm.rate)
|
# qty of "finished good" item yet to be received
|
||||||
|
qty_to_be_received_map = get_qty_to_be_received(purchase_orders)
|
||||||
|
|
||||||
|
for item in self.get('items'):
|
||||||
|
# reset raw_material cost
|
||||||
|
item.rm_supp_cost = 0
|
||||||
|
|
||||||
|
# qty of raw materials transferred to the supplier
|
||||||
|
transferred_raw_materials = get_subcontracted_raw_materials_from_se(item.purchase_order, item.item_code)
|
||||||
|
|
||||||
|
non_stock_items = get_non_stock_items(item.purchase_order, item.item_code)
|
||||||
|
|
||||||
|
item_key = '{}{}'.format(item.item_code, item.purchase_order)
|
||||||
|
|
||||||
|
fg_yet_to_be_received = qty_to_be_received_map.get(item_key)
|
||||||
|
|
||||||
|
raw_material_data = backflushed_raw_materials_map.get(item_key, {})
|
||||||
|
|
||||||
|
consumed_qty = raw_material_data.get('qty', 0)
|
||||||
|
consumed_serial_nos = raw_material_data.get('serial_nos', '')
|
||||||
|
consumed_batch_nos = raw_material_data.get('batch_nos', '')
|
||||||
|
|
||||||
|
transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code)
|
||||||
|
backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code)
|
||||||
|
|
||||||
|
for raw_material in transferred_raw_materials + non_stock_items:
|
||||||
|
transferred_qty = raw_material.qty
|
||||||
|
|
||||||
|
rm_qty_to_be_consumed = transferred_qty - consumed_qty
|
||||||
|
|
||||||
|
# backflush all remaining transferred qty in the last Purchase Receipt
|
||||||
|
if fg_yet_to_be_received == item.qty:
|
||||||
|
qty = rm_qty_to_be_consumed
|
||||||
|
else:
|
||||||
|
qty = (rm_qty_to_be_consumed / fg_yet_to_be_received) * item.qty
|
||||||
|
|
||||||
|
if frappe.get_cached_value('UOM', raw_material.stock_uom, 'must_be_whole_number'):
|
||||||
|
qty = frappe.utils.ceil(qty)
|
||||||
|
|
||||||
|
if qty > rm_qty_to_be_consumed:
|
||||||
|
qty = rm_qty_to_be_consumed
|
||||||
|
|
||||||
|
if not qty: continue
|
||||||
|
|
||||||
|
if raw_material.serial_nos:
|
||||||
|
set_serial_nos(raw_material, consumed_serial_nos, qty)
|
||||||
|
|
||||||
|
if raw_material.batch_nos:
|
||||||
|
batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
|
||||||
|
qty, transferred_batch_qty_map, backflushed_batch_qty_map)
|
||||||
|
for batch_data in batches_qty:
|
||||||
|
qty = batch_data['qty']
|
||||||
|
raw_material.batch_no = batch_data['batch']
|
||||||
|
self.append_raw_material_to_be_backflushed(item, raw_material, qty)
|
||||||
|
else:
|
||||||
|
self.append_raw_material_to_be_backflushed(item, raw_material, qty)
|
||||||
|
|
||||||
|
def append_raw_material_to_be_backflushed(self, fg_item_doc, raw_material_data, qty):
|
||||||
|
rm = self.append('supplied_items', {})
|
||||||
|
rm.update(raw_material_data)
|
||||||
|
|
||||||
|
rm.required_qty = qty
|
||||||
|
rm.consumed_qty = qty
|
||||||
|
|
||||||
|
if not raw_material_data.get('non_stock_item'):
|
||||||
|
from erpnext.stock.utils import get_incoming_rate
|
||||||
|
rm.rate = get_incoming_rate({
|
||||||
|
"item_code": raw_material_data.rm_item_code,
|
||||||
|
"warehouse": self.supplier_warehouse,
|
||||||
|
"posting_date": self.posting_date,
|
||||||
|
"posting_time": self.posting_time,
|
||||||
|
"qty": -1 * qty,
|
||||||
|
"serial_no": rm.serial_no
|
||||||
|
})
|
||||||
|
|
||||||
|
if not rm.rate:
|
||||||
|
rm.rate = get_valuation_rate(raw_material_data.item_code, self.supplier_warehouse,
|
||||||
|
self.doctype, self.name, currency=self.company_currency, company=self.company)
|
||||||
|
|
||||||
|
rm.amount = qty * flt(rm.rate)
|
||||||
|
fg_item_doc.rm_supp_cost += rm.amount
|
||||||
|
|
||||||
def update_raw_materials_supplied_based_on_bom(self, item, raw_material_table):
|
def update_raw_materials_supplied_based_on_bom(self, item, raw_material_table):
|
||||||
exploded_item = 1
|
exploded_item = 1
|
||||||
@ -387,9 +441,11 @@ class BuyingController(StockController):
|
|||||||
item_codes = list(set(item.item_code for item in
|
item_codes = list(set(item.item_code for item in
|
||||||
self.get("items")))
|
self.get("items")))
|
||||||
if item_codes:
|
if item_codes:
|
||||||
self._sub_contracted_items = [r[0] for r in frappe.db.sql("""select name
|
items = frappe.get_all('Item', filters={
|
||||||
from `tabItem` where name in (%s) and is_sub_contracted_item=1""" % \
|
'name': ['in', item_codes],
|
||||||
(", ".join((["%s"]*len(item_codes))),), item_codes)]
|
'is_sub_contracted_item': 1
|
||||||
|
})
|
||||||
|
self._sub_contracted_items = [item.name for item in items]
|
||||||
|
|
||||||
return self._sub_contracted_items
|
return self._sub_contracted_items
|
||||||
|
|
||||||
@ -722,28 +778,72 @@ def get_items_from_bom(item_code, bom, exploded_item=1):
|
|||||||
|
|
||||||
return bom_items
|
return bom_items
|
||||||
|
|
||||||
def get_subcontracted_raw_materials_from_se(purchase_orders):
|
def get_subcontracted_raw_materials_from_se(purchase_order, fg_item):
|
||||||
return frappe.db.sql("""
|
common_query = """
|
||||||
select
|
SELECT
|
||||||
sed.item_name, sed.item_code, sum(sed.qty) as qty, sed.description,
|
sed.item_code AS rm_item_code,
|
||||||
sed.stock_uom, sed.subcontracted_item as main_item_code, sed.serial_no, sed.batch_no
|
SUM(sed.qty) AS qty,
|
||||||
from `tabStock Entry` se,`tabStock Entry Detail` sed
|
sed.description,
|
||||||
where
|
sed.stock_uom,
|
||||||
se.name = sed.parent and se.docstatus=1 and se.purpose='Send to Subcontractor'
|
sed.subcontracted_item AS main_item_code,
|
||||||
and se.purchase_order in (%s) and ifnull(sed.t_warehouse, '') != ''
|
{serial_no_concat_syntax} AS serial_nos,
|
||||||
group by sed.item_code, sed.t_warehouse
|
{batch_no_concat_syntax} AS batch_nos
|
||||||
""" % (','.join(['%s'] * len(purchase_orders))), tuple(purchase_orders), as_dict=1)
|
FROM `tabStock Entry` se,`tabStock Entry Detail` sed
|
||||||
|
WHERE
|
||||||
|
se.name = sed.parent
|
||||||
|
AND se.docstatus=1
|
||||||
|
AND se.purpose='Send to Subcontractor'
|
||||||
|
AND se.purchase_order = %s
|
||||||
|
AND IFNULL(sed.t_warehouse, '') != ''
|
||||||
|
AND sed.subcontracted_item = %s
|
||||||
|
GROUP BY sed.item_code, sed.subcontracted_item
|
||||||
|
"""
|
||||||
|
raw_materials = frappe.db.multisql({
|
||||||
|
'mariadb': common_query.format(
|
||||||
|
serial_no_concat_syntax="GROUP_CONCAT(sed.serial_no)",
|
||||||
|
batch_no_concat_syntax="GROUP_CONCAT(sed.batch_no)"
|
||||||
|
),
|
||||||
|
'postgres': common_query.format(
|
||||||
|
serial_no_concat_syntax="STRING_AGG(sed.serial_no, ',')",
|
||||||
|
batch_no_concat_syntax="STRING_AGG(sed.batch_no, ',')"
|
||||||
|
)
|
||||||
|
}, (purchase_order, fg_item), as_dict=1)
|
||||||
|
|
||||||
def get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, purchase_receipt):
|
return raw_materials
|
||||||
return frappe._dict(frappe.db.sql("""
|
|
||||||
select
|
def get_backflushed_subcontracted_raw_materials(purchase_orders):
|
||||||
prsi.rm_item_code as item_code, sum(prsi.consumed_qty) as qty
|
common_query = """
|
||||||
from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi
|
SELECT
|
||||||
where
|
CONCAT(prsi.rm_item_code, pri.purchase_order) AS item_key,
|
||||||
pr.name = pri.parent and pr.name = prsi.parent and pri.purchase_order in (%s)
|
SUM(prsi.consumed_qty) AS qty,
|
||||||
and pri.item_code = prsi.main_item_code and pr.name != '%s' and pr.docstatus = 1
|
{serial_no_concat_syntax} AS serial_nos,
|
||||||
group by prsi.rm_item_code
|
{batch_no_concat_syntax} AS batch_nos
|
||||||
""" % (','.join(['%s'] * len(purchase_orders)), purchase_receipt), tuple(purchase_orders)))
|
FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi
|
||||||
|
WHERE
|
||||||
|
pr.name = pri.parent
|
||||||
|
AND pr.name = prsi.parent
|
||||||
|
AND pri.purchase_order IN %s
|
||||||
|
AND pri.item_code = prsi.main_item_code
|
||||||
|
AND pr.docstatus = 1
|
||||||
|
GROUP BY prsi.rm_item_code, pri.purchase_order
|
||||||
|
"""
|
||||||
|
|
||||||
|
backflushed_raw_materials = frappe.db.multisql({
|
||||||
|
'mariadb': common_query.format(
|
||||||
|
serial_no_concat_syntax="GROUP_CONCAT(prsi.serial_no)",
|
||||||
|
batch_no_concat_syntax="GROUP_CONCAT(prsi.batch_no)"
|
||||||
|
),
|
||||||
|
'postgres': common_query.format(
|
||||||
|
serial_no_concat_syntax="STRING_AGG(prsi.serial_no, ',')",
|
||||||
|
batch_no_concat_syntax="STRING_AGG(prsi.batch_no, ',')"
|
||||||
|
)
|
||||||
|
}, (purchase_orders, ), as_dict=1)
|
||||||
|
|
||||||
|
backflushed_raw_materials_map = frappe._dict()
|
||||||
|
for item in backflushed_raw_materials:
|
||||||
|
backflushed_raw_materials_map.setdefault(item.item_key, item)
|
||||||
|
|
||||||
|
return backflushed_raw_materials_map
|
||||||
|
|
||||||
def get_asset_item_details(asset_items):
|
def get_asset_item_details(asset_items):
|
||||||
asset_items_data = {}
|
asset_items_data = {}
|
||||||
@ -776,3 +876,125 @@ def validate_item_type(doc, fieldname, message):
|
|||||||
error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master".format(items, message))
|
error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master".format(items, message))
|
||||||
|
|
||||||
frappe.throw(error_message)
|
frappe.throw(error_message)
|
||||||
|
|
||||||
|
def get_qty_to_be_received(purchase_orders):
|
||||||
|
return frappe._dict(frappe.db.sql("""
|
||||||
|
SELECT CONCAT(poi.`item_code`, poi.`parent`) AS item_key,
|
||||||
|
SUM(poi.`qty`) - SUM(poi.`received_qty`) AS qty_to_be_received
|
||||||
|
FROM `tabPurchase Order Item` poi
|
||||||
|
WHERE
|
||||||
|
poi.`parent` in %s
|
||||||
|
GROUP BY poi.`item_code`, poi.`parent`
|
||||||
|
HAVING SUM(poi.`qty`) > SUM(poi.`received_qty`)
|
||||||
|
""", (purchase_orders)))
|
||||||
|
|
||||||
|
def get_non_stock_items(purchase_order, fg_item_code):
|
||||||
|
return frappe.db.sql("""
|
||||||
|
SELECT
|
||||||
|
pois.main_item_code,
|
||||||
|
pois.rm_item_code,
|
||||||
|
item.description,
|
||||||
|
pois.required_qty AS qty,
|
||||||
|
pois.rate,
|
||||||
|
1 as non_stock_item,
|
||||||
|
pois.stock_uom
|
||||||
|
FROM `tabPurchase Order Item Supplied` pois, `tabItem` item
|
||||||
|
WHERE
|
||||||
|
pois.`rm_item_code` = item.`name`
|
||||||
|
AND item.is_stock_item = 0
|
||||||
|
AND pois.`parent` = %s
|
||||||
|
AND pois.`main_item_code` = %s
|
||||||
|
""", (purchase_order, fg_item_code), as_dict=1)
|
||||||
|
|
||||||
|
|
||||||
|
def set_serial_nos(raw_material, consumed_serial_nos, qty):
|
||||||
|
serial_nos = set(get_serial_nos(raw_material.serial_nos)) - \
|
||||||
|
set(get_serial_nos(consumed_serial_nos))
|
||||||
|
if serial_nos and qty <= len(serial_nos):
|
||||||
|
raw_material.serial_no = '\n'.join(list(serial_nos)[0:frappe.utils.cint(qty)])
|
||||||
|
|
||||||
|
def get_transferred_batch_qty_map(purchase_order, fg_item):
|
||||||
|
# returns
|
||||||
|
# {
|
||||||
|
# (item_code, fg_code): {
|
||||||
|
# batch1: 10, # qty
|
||||||
|
# batch2: 16
|
||||||
|
# },
|
||||||
|
# }
|
||||||
|
transferred_batch_qty_map = {}
|
||||||
|
transferred_batches = frappe.db.sql("""
|
||||||
|
SELECT
|
||||||
|
sed.batch_no,
|
||||||
|
SUM(sed.qty) AS qty,
|
||||||
|
sed.item_code
|
||||||
|
FROM `tabStock Entry` se,`tabStock Entry Detail` sed
|
||||||
|
WHERE
|
||||||
|
se.name = sed.parent
|
||||||
|
AND se.docstatus=1
|
||||||
|
AND se.purpose='Send to Subcontractor'
|
||||||
|
AND se.purchase_order = %s
|
||||||
|
AND sed.subcontracted_item = %s
|
||||||
|
AND sed.batch_no IS NOT NULL
|
||||||
|
GROUP BY
|
||||||
|
sed.batch_no,
|
||||||
|
sed.item_code
|
||||||
|
""", (purchase_order, fg_item), as_dict=1)
|
||||||
|
|
||||||
|
for batch_data in transferred_batches:
|
||||||
|
transferred_batch_qty_map.setdefault((batch_data.item_code, fg_item), {})
|
||||||
|
transferred_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty
|
||||||
|
|
||||||
|
return transferred_batch_qty_map
|
||||||
|
|
||||||
|
def get_backflushed_batch_qty_map(purchase_order, fg_item):
|
||||||
|
# returns
|
||||||
|
# {
|
||||||
|
# (item_code, fg_code): {
|
||||||
|
# batch1: 10, # qty
|
||||||
|
# batch2: 16
|
||||||
|
# },
|
||||||
|
# }
|
||||||
|
backflushed_batch_qty_map = {}
|
||||||
|
backflushed_batches = frappe.db.sql("""
|
||||||
|
SELECT
|
||||||
|
pris.batch_no,
|
||||||
|
SUM(pris.consumed_qty) AS qty,
|
||||||
|
pris.rm_item_code AS item_code
|
||||||
|
FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` pris
|
||||||
|
WHERE
|
||||||
|
pr.name = pri.parent
|
||||||
|
AND pri.parent = pris.parent
|
||||||
|
AND pri.purchase_order = %s
|
||||||
|
AND pri.item_code = pris.main_item_code
|
||||||
|
AND pr.docstatus = 1
|
||||||
|
AND pris.main_item_code = %s
|
||||||
|
AND pris.batch_no IS NOT NULL
|
||||||
|
GROUP BY
|
||||||
|
pris.rm_item_code, pris.batch_no
|
||||||
|
""", (purchase_order, fg_item), as_dict=1)
|
||||||
|
|
||||||
|
for batch_data in backflushed_batches:
|
||||||
|
backflushed_batch_qty_map.setdefault((batch_data.item_code, fg_item), {})
|
||||||
|
backflushed_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty
|
||||||
|
|
||||||
|
return backflushed_batch_qty_map
|
||||||
|
|
||||||
|
def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batch_qty_map):
|
||||||
|
# Returns available batches to be backflushed based on requirements
|
||||||
|
transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {})
|
||||||
|
backflushed_batches = backflushed_batch_qty_map.get((item_code, fg_item), {})
|
||||||
|
|
||||||
|
available_batches = []
|
||||||
|
|
||||||
|
for (batch, transferred_qty) in transferred_batches.items():
|
||||||
|
backflushed_qty = backflushed_batches.get(batch, 0)
|
||||||
|
available_qty = transferred_qty - backflushed_qty
|
||||||
|
|
||||||
|
if available_qty >= required_qty:
|
||||||
|
available_batches.append({'batch': batch, 'qty': required_qty})
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
available_batches.append({'batch': batch, 'qty': available_qty})
|
||||||
|
required_qty -= available_qty
|
||||||
|
|
||||||
|
return available_batches
|
||||||
@ -41,7 +41,8 @@ class EmailCampaign(Document):
|
|||||||
email_campaign_exists = frappe.db.exists("Email Campaign", {
|
email_campaign_exists = frappe.db.exists("Email Campaign", {
|
||||||
"campaign_name": self.campaign_name,
|
"campaign_name": self.campaign_name,
|
||||||
"recipient": self.recipient,
|
"recipient": self.recipient,
|
||||||
"status": ("in", ["In Progress", "Scheduled"])
|
"status": ("in", ["In Progress", "Scheduled"]),
|
||||||
|
"name": ("!=", self.name)
|
||||||
})
|
})
|
||||||
if email_campaign_exists:
|
if email_campaign_exists:
|
||||||
frappe.throw(_("The Campaign '{0}' already exists for the {1} '{2}'").format(self.campaign_name, self.email_campaign_for, self.recipient))
|
frappe.throw(_("The Campaign '{0}' already exists for the {1} '{2}'").format(self.campaign_name, self.email_campaign_for, self.recipient))
|
||||||
@ -78,7 +79,7 @@ def send_mail(entry, email_campaign):
|
|||||||
comm = make(
|
comm = make(
|
||||||
doctype = "Email Campaign",
|
doctype = "Email Campaign",
|
||||||
name = email_campaign.name,
|
name = email_campaign.name,
|
||||||
subject = email_template.get("subject"),
|
subject = frappe.render_template(email_template.get("subject"), context),
|
||||||
content = frappe.render_template(email_template.get("response"), context),
|
content = frappe.render_template(email_template.get("response"), context),
|
||||||
sender = sender,
|
sender = sender,
|
||||||
recipients = recipient,
|
recipients = recipient,
|
||||||
|
|||||||
@ -130,10 +130,10 @@ class Opportunity(TransactionBase):
|
|||||||
|
|
||||||
def has_lost_quotation(self):
|
def has_lost_quotation(self):
|
||||||
lost_quotation = frappe.db.sql("""
|
lost_quotation = frappe.db.sql("""
|
||||||
select q.name
|
select name
|
||||||
from `tabQuotation` q, `tabQuotation Item` qi
|
from `tabQuotation`
|
||||||
where q.name = qi.parent and q.docstatus=1
|
where docstatus=1
|
||||||
and qi.prevdoc_docname =%s and q.status = 'Lost'
|
and opportunity =%s and status = 'Lost'
|
||||||
""", self.name)
|
""", self.name)
|
||||||
if lost_quotation:
|
if lost_quotation:
|
||||||
if self.has_active_quotation():
|
if self.has_active_quotation():
|
||||||
|
|||||||
@ -71,7 +71,7 @@ class ProgramEnrollment(Document):
|
|||||||
def create_course_enrollments(self):
|
def create_course_enrollments(self):
|
||||||
student = frappe.get_doc("Student", self.student)
|
student = frappe.get_doc("Student", self.student)
|
||||||
program = frappe.get_doc("Program", self.program)
|
program = frappe.get_doc("Program", self.program)
|
||||||
course_list = [course.course for course in program.get_all_children()]
|
course_list = [course.course for course in program.courses]
|
||||||
for course_name in course_list:
|
for course_name in course_list:
|
||||||
student.enroll_in_course(course_name=course_name, program_enrollment=self.name)
|
student.enroll_in_course(course_name=course_name, program_enrollment=self.name)
|
||||||
|
|
||||||
|
|||||||
@ -40,7 +40,7 @@ class Student(Document):
|
|||||||
frappe.throw(_("Student {0} exist against student applicant {1}").format(student[0][0], self.student_applicant))
|
frappe.throw(_("Student {0} exist against student applicant {1}").format(student[0][0], self.student_applicant))
|
||||||
|
|
||||||
def after_insert(self):
|
def after_insert(self):
|
||||||
if not frappe.get_single('Education Settings').user_creation_skip:
|
if not frappe.get_single('Education Settings').get('user_creation_skip'):
|
||||||
self.create_student_user()
|
self.create_student_user()
|
||||||
|
|
||||||
def create_student_user(self):
|
def create_student_user(self):
|
||||||
|
|||||||
@ -63,10 +63,11 @@ def updating_rate(self):
|
|||||||
item_code=%s""",(self.template, self.rate, self.item))
|
item_code=%s""",(self.template, self.rate, self.item))
|
||||||
|
|
||||||
def create_item_from_template(doc):
|
def create_item_from_template(doc):
|
||||||
|
disabled = 1
|
||||||
|
|
||||||
if(doc.is_billable == 1):
|
if(doc.is_billable == 1):
|
||||||
disabled = 0
|
disabled = 0
|
||||||
else:
|
|
||||||
disabled = 1
|
|
||||||
#insert item
|
#insert item
|
||||||
item = frappe.get_doc({
|
item = frappe.get_doc({
|
||||||
"doctype": "Item",
|
"doctype": "Item",
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import today, now_datetime
|
from frappe.utils import today, now_datetime, getdate
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.desk.reportview import get_match_cond
|
from frappe.desk.reportview import get_match_cond
|
||||||
|
|
||||||
@ -15,11 +15,20 @@ class InpatientRecord(Document):
|
|||||||
frappe.db.set_value("Patient", self.patient, "inpatient_record", self.name)
|
frappe.db.set_value("Patient", self.patient, "inpatient_record", self.name)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
self.validate_dates()
|
||||||
self.validate_already_scheduled_or_admitted()
|
self.validate_already_scheduled_or_admitted()
|
||||||
if self.status == "Discharged":
|
if self.status == "Discharged":
|
||||||
frappe.db.set_value("Patient", self.patient, "inpatient_status", None)
|
frappe.db.set_value("Patient", self.patient, "inpatient_status", None)
|
||||||
frappe.db.set_value("Patient", self.patient, "inpatient_record", None)
|
frappe.db.set_value("Patient", self.patient, "inpatient_record", None)
|
||||||
|
|
||||||
|
def validate_dates(self):
|
||||||
|
if (getdate(self.scheduled_date) < getdate(today())) or \
|
||||||
|
(getdate(self.admitted_datetime) < getdate(today())):
|
||||||
|
frappe.throw(_("Scheduled and Admitted dates can not be less than today"))
|
||||||
|
if (getdate(self.expected_discharge) < getdate(self.scheduled_date)) or \
|
||||||
|
(getdate(self.discharge_date) < getdate(self.scheduled_date)):
|
||||||
|
frappe.throw(_("Expected and Discharge dates cannot be less than Admission Schedule date"))
|
||||||
|
|
||||||
def validate_already_scheduled_or_admitted(self):
|
def validate_already_scheduled_or_admitted(self):
|
||||||
query = """
|
query = """
|
||||||
select name, status
|
select name, status
|
||||||
|
|||||||
@ -40,8 +40,6 @@ after_install = "erpnext.setup.install.after_install"
|
|||||||
boot_session = "erpnext.startup.boot.boot_session"
|
boot_session = "erpnext.startup.boot.boot_session"
|
||||||
notification_config = "erpnext.startup.notifications.get_notification_config"
|
notification_config = "erpnext.startup.notifications.get_notification_config"
|
||||||
get_help_messages = "erpnext.utilities.activation.get_help_messages"
|
get_help_messages = "erpnext.utilities.activation.get_help_messages"
|
||||||
get_user_progress_slides = "erpnext.utilities.user_progress.get_user_progress_slides"
|
|
||||||
update_and_get_user_progress = "erpnext.utilities.user_progress_utils.update_default_domain_actions_and_get_state"
|
|
||||||
leaderboards = "erpnext.startup.leaderboard.get_leaderboards"
|
leaderboards = "erpnext.startup.leaderboard.get_leaderboards"
|
||||||
|
|
||||||
|
|
||||||
@ -302,7 +300,7 @@ scheduler_events = {
|
|||||||
"erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status",
|
"erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status",
|
||||||
"erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts",
|
"erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts",
|
||||||
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status",
|
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status",
|
||||||
"erpnext.selling.doctype.quotation.set_expired_status"
|
"erpnext.selling.doctype.quotation.quotation.set_expired_status"
|
||||||
],
|
],
|
||||||
"daily_long": [
|
"daily_long": [
|
||||||
"erpnext.setup.doctype.email_digest.email_digest.send",
|
"erpnext.setup.doctype.email_digest.email_digest.send",
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
from frappe.utils import getdate, validate_email_address, today, add_years, format_datetime
|
from frappe.utils import getdate, validate_email_address, today, add_years, format_datetime, cstr
|
||||||
from frappe.model.naming import set_name_by_naming_series
|
from frappe.model.naming import set_name_by_naming_series
|
||||||
from frappe import throw, _, scrub
|
from frappe import throw, _, scrub
|
||||||
from frappe.permissions import add_user_permission, remove_user_permission, \
|
from frappe.permissions import add_user_permission, remove_user_permission, \
|
||||||
@ -152,8 +152,8 @@ class Employee(NestedSet):
|
|||||||
elif self.date_of_retirement and self.date_of_joining and (getdate(self.date_of_retirement) <= getdate(self.date_of_joining)):
|
elif self.date_of_retirement and self.date_of_joining and (getdate(self.date_of_retirement) <= getdate(self.date_of_joining)):
|
||||||
throw(_("Date Of Retirement must be greater than Date of Joining"))
|
throw(_("Date Of Retirement must be greater than Date of Joining"))
|
||||||
|
|
||||||
elif self.relieving_date and self.date_of_joining and (getdate(self.relieving_date) <= getdate(self.date_of_joining)):
|
elif self.relieving_date and self.date_of_joining and (getdate(self.relieving_date) < getdate(self.date_of_joining)):
|
||||||
throw(_("Relieving Date must be greater than Date of Joining"))
|
throw(_("Relieving Date must be greater than or equal to Date of Joining"))
|
||||||
|
|
||||||
elif self.contract_end_date and self.date_of_joining and (getdate(self.contract_end_date) <= getdate(self.date_of_joining)):
|
elif self.contract_end_date and self.date_of_joining and (getdate(self.contract_end_date) <= getdate(self.date_of_joining)):
|
||||||
throw(_("Contract End Date must be greater than Date of Joining"))
|
throw(_("Contract End Date must be greater than Date of Joining"))
|
||||||
@ -218,8 +218,8 @@ class Employee(NestedSet):
|
|||||||
|
|
||||||
def reset_employee_emails_cache(self):
|
def reset_employee_emails_cache(self):
|
||||||
prev_doc = self.get_doc_before_save() or {}
|
prev_doc = self.get_doc_before_save() or {}
|
||||||
cell_number = self.get('cell_number')
|
cell_number = cstr(self.get('cell_number'))
|
||||||
prev_number = prev_doc.get('cell_number')
|
prev_number = cstr(prev_doc.get('cell_number'))
|
||||||
if (cell_number != prev_number or
|
if (cell_number != prev_number or
|
||||||
self.get('user_id') != prev_doc.get('user_id')):
|
self.get('user_id') != prev_doc.get('user_id')):
|
||||||
frappe.cache().hdel('employees_with_number', cell_number)
|
frappe.cache().hdel('employees_with_number', cell_number)
|
||||||
|
|||||||
@ -31,7 +31,11 @@ frappe.ui.form.on('Payroll Entry', {
|
|||||||
}
|
}
|
||||||
if ((frm.doc.employees || []).length) {
|
if ((frm.doc.employees || []).length) {
|
||||||
frm.page.set_primary_action(__('Create Salary Slips'), () => {
|
frm.page.set_primary_action(__('Create Salary Slips'), () => {
|
||||||
frm.save('Submit');
|
frm.save('Submit').then(()=>{
|
||||||
|
frm.page.clear_primary_action();
|
||||||
|
frm.refresh();
|
||||||
|
frm.events.refresh(frm);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,231 +1,82 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2016-12-20 15:32:25.078334",
|
"creation": "2016-12-20 15:32:25.078334",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"payment_date",
|
||||||
|
"principal_amount",
|
||||||
|
"interest_amount",
|
||||||
|
"total_payment",
|
||||||
|
"balance_loan_amount",
|
||||||
|
"paid"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_on_submit": 1,
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
"columns": 2,
|
||||||
"fieldname": "payment_date",
|
"fieldname": "payment_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
"label": "Payment Date"
|
||||||
"label": "Payment Date",
|
|
||||||
"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": 2,
|
"columns": 2,
|
||||||
"fieldname": "principal_amount",
|
"fieldname": "principal_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Principal Amount",
|
"label": "Principal Amount",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"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": 2,
|
"columns": 2,
|
||||||
"fieldname": "interest_amount",
|
"fieldname": "interest_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Interest Amount",
|
"label": "Interest Amount",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"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": 2,
|
"columns": 2,
|
||||||
"fieldname": "total_payment",
|
"fieldname": "total_payment",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Total Payment",
|
"label": "Total Payment",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"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": 2,
|
"columns": 2,
|
||||||
"fieldname": "balance_loan_amount",
|
"fieldname": "balance_loan_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Balance Loan Amount",
|
"label": "Balance Loan Amount",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"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,
|
"default": "0",
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "paid",
|
"fieldname": "paid",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Paid",
|
"label": "Paid",
|
||||||
"length": 0,
|
"read_only": 1
|
||||||
"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,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"modified": "2019-10-29 11:45:10.694557",
|
||||||
"modified": "2018-03-30 17:37:31.834792",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Repayment Schedule",
|
"name": "Repayment Schedule",
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"track_changes": 1
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
||||||
@ -44,6 +44,8 @@ def make_sales_order(source_name):
|
|||||||
target.item_name = item.get("item_name")
|
target.item_name = item.get("item_name")
|
||||||
target.description = item.get("description")
|
target.description = item.get("description")
|
||||||
target.uom = item.get("stock_uom")
|
target.uom = item.get("stock_uom")
|
||||||
|
target.against_blanket_order = 1
|
||||||
|
target.blanket_order = source_name
|
||||||
|
|
||||||
target_doc = get_mapped_doc("Blanket Order", source_name, {
|
target_doc = get_mapped_doc("Blanket Order", source_name, {
|
||||||
"Blanket Order": {
|
"Blanket Order": {
|
||||||
@ -71,6 +73,8 @@ def make_purchase_order(source_name):
|
|||||||
target.description = item.get("description")
|
target.description = item.get("description")
|
||||||
target.uom = item.get("stock_uom")
|
target.uom = item.get("stock_uom")
|
||||||
target.warehouse = item.get("default_warehouse")
|
target.warehouse = item.get("default_warehouse")
|
||||||
|
target.against_blanket_order = 1
|
||||||
|
target.blanket_order = source_name
|
||||||
|
|
||||||
target_doc = get_mapped_doc("Blanket Order", source_name, {
|
target_doc = get_mapped_doc("Blanket Order", source_name, {
|
||||||
"Blanket Order": {
|
"Blanket Order": {
|
||||||
|
|||||||
@ -606,6 +606,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite
|
|||||||
item.image,
|
item.image,
|
||||||
bom.project,
|
bom.project,
|
||||||
item.stock_uom,
|
item.stock_uom,
|
||||||
|
item.item_group,
|
||||||
item.allow_alternative_item,
|
item.allow_alternative_item,
|
||||||
item_default.default_warehouse,
|
item_default.default_warehouse,
|
||||||
item_default.expense_account as expense_account,
|
item_default.expense_account as expense_account,
|
||||||
|
|||||||
@ -9,6 +9,7 @@ from frappe import _
|
|||||||
from six import string_types
|
from six import string_types
|
||||||
from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order
|
from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
import click
|
||||||
|
|
||||||
class BOMUpdateTool(Document):
|
class BOMUpdateTool(Document):
|
||||||
def replace_bom(self):
|
def replace_bom(self):
|
||||||
@ -17,7 +18,8 @@ class BOMUpdateTool(Document):
|
|||||||
frappe.cache().delete_key('bom_children')
|
frappe.cache().delete_key('bom_children')
|
||||||
bom_list = self.get_parent_boms(self.new_bom)
|
bom_list = self.get_parent_boms(self.new_bom)
|
||||||
updated_bom = []
|
updated_bom = []
|
||||||
|
with click.progressbar(bom_list) as bom_list:
|
||||||
|
pass
|
||||||
for bom in bom_list:
|
for bom in bom_list:
|
||||||
try:
|
try:
|
||||||
bom_obj = frappe.get_cached_doc('BOM', bom)
|
bom_obj = frappe.get_cached_doc('BOM', bom)
|
||||||
|
|||||||
@ -4,10 +4,16 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
|
import datetime
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import flt, time_diff_in_hours, get_datetime
|
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import (flt, cint, time_diff_in_hours, get_datetime, getdate,
|
||||||
|
get_time, add_to_date, time_diff, add_days, get_datetime_str)
|
||||||
|
|
||||||
|
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
|
||||||
|
|
||||||
|
class OverlapError(frappe.ValidationError): pass
|
||||||
|
|
||||||
class JobCard(Document):
|
class JobCard(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@ -26,7 +32,7 @@ class JobCard(Document):
|
|||||||
data = self.get_overlap_for(d)
|
data = self.get_overlap_for(d)
|
||||||
if data:
|
if data:
|
||||||
frappe.throw(_("Row {0}: From Time and To Time of {1} is overlapping with {2}")
|
frappe.throw(_("Row {0}: From Time and To Time of {1} is overlapping with {2}")
|
||||||
.format(d.idx, self.name, data.name))
|
.format(d.idx, self.name, data.name), OverlapError)
|
||||||
|
|
||||||
if d.from_time and d.to_time:
|
if d.from_time and d.to_time:
|
||||||
d.time_in_mins = time_diff_in_hours(d.to_time, d.from_time) * 60
|
d.time_in_mins = time_diff_in_hours(d.to_time, d.from_time) * 60
|
||||||
@ -35,27 +41,120 @@ class JobCard(Document):
|
|||||||
if d.completed_qty:
|
if d.completed_qty:
|
||||||
self.total_completed_qty += d.completed_qty
|
self.total_completed_qty += d.completed_qty
|
||||||
|
|
||||||
def get_overlap_for(self, args):
|
def get_overlap_for(self, args, check_next_available_slot=False):
|
||||||
existing = frappe.db.sql("""select jc.name as name from
|
production_capacity = 1
|
||||||
|
|
||||||
|
if self.workstation:
|
||||||
|
production_capacity = frappe.get_cached_value("Workstation",
|
||||||
|
self.workstation, 'production_capacity') or 1
|
||||||
|
validate_overlap_for = " and jc.workstation = %(workstation)s "
|
||||||
|
|
||||||
|
if self.employee:
|
||||||
|
# override capacity for employee
|
||||||
|
production_capacity = 1
|
||||||
|
validate_overlap_for = " and jc.employee = %(employee)s "
|
||||||
|
|
||||||
|
extra_cond = ''
|
||||||
|
if check_next_available_slot:
|
||||||
|
extra_cond = " or (%(from_time)s <= jctl.from_time and %(to_time)s <= jctl.to_time)"
|
||||||
|
|
||||||
|
existing = frappe.db.sql("""select jc.name as name, jctl.to_time from
|
||||||
`tabJob Card Time Log` jctl, `tabJob Card` jc where jctl.parent = jc.name and
|
`tabJob Card Time Log` jctl, `tabJob Card` jc where jctl.parent = jc.name and
|
||||||
(
|
(
|
||||||
(%(from_time)s > jctl.from_time and %(from_time)s < jctl.to_time) or
|
(%(from_time)s > jctl.from_time and %(from_time)s < jctl.to_time) or
|
||||||
(%(to_time)s > jctl.from_time and %(to_time)s < jctl.to_time) or
|
(%(to_time)s > jctl.from_time and %(to_time)s < jctl.to_time) or
|
||||||
(%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time))
|
(%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time) {0}
|
||||||
and jctl.name!=%(name)s
|
)
|
||||||
and jc.name!=%(parent)s
|
and jctl.name != %(name)s and jc.name != %(parent)s and jc.docstatus < 2 {1}
|
||||||
and jc.docstatus < 2
|
order by jctl.to_time desc limit 1""".format(extra_cond, validate_overlap_for),
|
||||||
and jc.employee = %(employee)s """,
|
|
||||||
{
|
{
|
||||||
"from_time": args.from_time,
|
"from_time": args.from_time,
|
||||||
"to_time": args.to_time,
|
"to_time": args.to_time,
|
||||||
"name": args.name or "No Name",
|
"name": args.name or "No Name",
|
||||||
"parent": args.parent or "No Name",
|
"parent": args.parent or "No Name",
|
||||||
"employee": self.employee
|
"employee": self.employee,
|
||||||
|
"workstation": self.workstation
|
||||||
}, as_dict=True)
|
}, as_dict=True)
|
||||||
|
|
||||||
|
if existing and production_capacity > len(existing):
|
||||||
|
return
|
||||||
|
|
||||||
return existing[0] if existing else None
|
return existing[0] if existing else None
|
||||||
|
|
||||||
|
def schedule_time_logs(self, row):
|
||||||
|
row.remaining_time_in_mins = row.time_in_mins
|
||||||
|
while row.remaining_time_in_mins > 0:
|
||||||
|
args = frappe._dict({
|
||||||
|
"from_time": row.planned_start_time,
|
||||||
|
"to_time": row.planned_end_time
|
||||||
|
})
|
||||||
|
|
||||||
|
self.validate_overlap_for_workstation(args, row)
|
||||||
|
self.check_workstation_time(row)
|
||||||
|
|
||||||
|
def validate_overlap_for_workstation(self, args, row):
|
||||||
|
# get the last record based on the to time from the job card
|
||||||
|
data = self.get_overlap_for(args, check_next_available_slot=True)
|
||||||
|
if data:
|
||||||
|
row.planned_start_time = get_datetime(data.to_time + get_mins_between_operations())
|
||||||
|
|
||||||
|
def check_workstation_time(self, row):
|
||||||
|
workstation_doc = frappe.get_cached_doc("Workstation", self.workstation)
|
||||||
|
if (not workstation_doc.working_hours or
|
||||||
|
cint(frappe.db.get_single_value("Manufacturing Settings", "allow_overtime"))):
|
||||||
|
row.remaining_time_in_mins -= time_diff_in_minutes(row.planned_end_time,
|
||||||
|
row.planned_start_time)
|
||||||
|
|
||||||
|
self.update_time_logs(row)
|
||||||
|
return
|
||||||
|
|
||||||
|
start_date = getdate(row.planned_start_time)
|
||||||
|
start_time = get_time(row.planned_start_time)
|
||||||
|
|
||||||
|
new_start_date = workstation_doc.validate_workstation_holiday(start_date)
|
||||||
|
|
||||||
|
if new_start_date != start_date:
|
||||||
|
row.planned_start_time = datetime.datetime.combine(new_start_date, start_time)
|
||||||
|
start_date = new_start_date
|
||||||
|
|
||||||
|
total_idx = len(workstation_doc.working_hours)
|
||||||
|
|
||||||
|
for i, time_slot in enumerate(workstation_doc.working_hours):
|
||||||
|
workstation_start_time = datetime.datetime.combine(start_date, get_time(time_slot.start_time))
|
||||||
|
workstation_end_time = datetime.datetime.combine(start_date, get_time(time_slot.end_time))
|
||||||
|
|
||||||
|
if (get_datetime(row.planned_start_time) >= workstation_start_time and
|
||||||
|
get_datetime(row.planned_start_time) <= workstation_end_time):
|
||||||
|
time_in_mins = time_diff_in_minutes(workstation_end_time, row.planned_start_time)
|
||||||
|
|
||||||
|
# If remaining time fit in workstation time logs else split hours as per workstation time
|
||||||
|
if time_in_mins > row.remaining_time_in_mins:
|
||||||
|
row.planned_end_time = add_to_date(row.planned_start_time,
|
||||||
|
minutes=row.remaining_time_in_mins)
|
||||||
|
row.remaining_time_in_mins = 0
|
||||||
|
else:
|
||||||
|
row.planned_end_time = add_to_date(row.planned_start_time, minutes=time_in_mins)
|
||||||
|
row.remaining_time_in_mins -= time_in_mins
|
||||||
|
|
||||||
|
self.update_time_logs(row)
|
||||||
|
|
||||||
|
if total_idx != (i+1) and row.remaining_time_in_mins > 0:
|
||||||
|
row.planned_start_time = datetime.datetime.combine(start_date,
|
||||||
|
get_time(workstation_doc.working_hours[i+1].start_time))
|
||||||
|
|
||||||
|
if row.remaining_time_in_mins > 0:
|
||||||
|
start_date = add_days(start_date, 1)
|
||||||
|
row.planned_start_time = datetime.datetime.combine(start_date,
|
||||||
|
get_time(workstation_doc.working_hours[0].start_time))
|
||||||
|
|
||||||
|
def update_time_logs(self, row):
|
||||||
|
self.append("time_logs", {
|
||||||
|
"from_time": row.planned_start_time,
|
||||||
|
"to_time": row.planned_end_time,
|
||||||
|
"completed_qty": 0,
|
||||||
|
"time_in_mins": time_diff_in_minutes(row.planned_end_time, row.planned_start_time),
|
||||||
|
})
|
||||||
|
|
||||||
def get_required_items(self):
|
def get_required_items(self):
|
||||||
if not self.get('work_order'):
|
if not self.get('work_order'):
|
||||||
return
|
return
|
||||||
@ -251,3 +350,6 @@ def make_stock_entry(source_name, target_doc=None):
|
|||||||
}, target_doc, set_missing_values)
|
}, target_doc, set_missing_values)
|
||||||
|
|
||||||
return doclist
|
return doclist
|
||||||
|
|
||||||
|
def time_diff_in_minutes(string_ed_date, string_st_date):
|
||||||
|
return time_diff(string_ed_date, string_st_date).total_seconds() / 60
|
||||||
@ -1,585 +1,178 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2014-11-27 14:12:07.542534",
|
"creation": "2014-11-27 14:12:07.542534",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Document",
|
"document_type": "Document",
|
||||||
"editable_grid": 0,
|
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"raw_materials_consumption_section",
|
||||||
|
"material_consumption",
|
||||||
|
"column_break_3",
|
||||||
|
"backflush_raw_materials_based_on",
|
||||||
|
"capacity_planning",
|
||||||
|
"disable_capacity_planning",
|
||||||
|
"allow_overtime",
|
||||||
|
"allow_production_on_holidays",
|
||||||
|
"column_break_5",
|
||||||
|
"capacity_planning_for_days",
|
||||||
|
"mins_between_operations",
|
||||||
|
"section_break_6",
|
||||||
|
"default_wip_warehouse",
|
||||||
|
"default_fg_warehouse",
|
||||||
|
"column_break_11",
|
||||||
|
"default_scrap_warehouse",
|
||||||
|
"over_production_for_sales_and_work_order_section",
|
||||||
|
"overproduction_percentage_for_sales_order",
|
||||||
|
"column_break_16",
|
||||||
|
"overproduction_percentage_for_work_order",
|
||||||
|
"other_settings_section",
|
||||||
|
"update_bom_costs_automatically"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "capacity_planning",
|
"fieldname": "capacity_planning",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"label": "Capacity Planning"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Capacity Planning",
|
|
||||||
"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,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
"depends_on": "eval:!doc.disable_capacity_planning",
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"description": "Disables creation of time logs against Work Orders. Operations shall not be tracked against Work Order",
|
|
||||||
"fieldname": "disable_capacity_planning",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"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": "Disable Capacity Planning and Time Tracking",
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"description": "Plan time logs outside Workstation Working Hours.",
|
"description": "Plan time logs outside Workstation Working Hours.",
|
||||||
"fieldname": "allow_overtime",
|
"fieldname": "allow_overtime",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"label": "Allow Overtime"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Allow Overtime",
|
|
||||||
"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,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
"depends_on": "eval:!doc.disable_capacity_planning",
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "",
|
|
||||||
"fieldname": "allow_production_on_holidays",
|
"fieldname": "allow_production_on_holidays",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
"label": "Allow Production on Holidays"
|
||||||
"label": "Allow Production on Holidays",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "",
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_3",
|
"fieldname": "column_break_3",
|
||||||
"fieldtype": "Column Break",
|
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "30",
|
"default": "30",
|
||||||
|
"depends_on": "eval:!doc.disable_capacity_planning",
|
||||||
"description": "Try planning operations for X days in advance.",
|
"description": "Try planning operations for X days in advance.",
|
||||||
"fieldname": "capacity_planning_for_days",
|
"fieldname": "capacity_planning_for_days",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"hidden": 0,
|
"label": "Capacity Planning For (Days)"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Capacity Planning For (Days)",
|
|
||||||
"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,
|
"depends_on": "eval:!doc.disable_capacity_planning",
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"description": "Default 10 mins",
|
"description": "Default 10 mins",
|
||||||
"fieldname": "mins_between_operations",
|
"fieldname": "mins_between_operations",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"hidden": 0,
|
"label": "Time Between Operations (in mins)"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Time Between Operations (in mins)",
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_6",
|
"fieldname": "section_break_6",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"label": "Default Warehouses for Production"
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "overproduction_percentage_for_sales_order",
|
"fieldname": "overproduction_percentage_for_sales_order",
|
||||||
"fieldtype": "Percent",
|
"fieldtype": "Percent",
|
||||||
"hidden": 0,
|
"label": "Overproduction Percentage For Sales Order"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Overproduction Percentage For Sales Order",
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "overproduction_percentage_for_work_order",
|
"fieldname": "overproduction_percentage_for_work_order",
|
||||||
"fieldtype": "Percent",
|
"fieldtype": "Percent",
|
||||||
"hidden": 0,
|
"label": "Overproduction Percentage For Work Order"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Overproduction Percentage For Work Order",
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "BOM",
|
"default": "BOM",
|
||||||
"fieldname": "backflush_raw_materials_based_on",
|
"fieldname": "backflush_raw_materials_based_on",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"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": "Backflush Raw Materials Based On",
|
"label": "Backflush Raw Materials Based On",
|
||||||
"length": 0,
|
"options": "BOM\nMaterial Transferred for Manufacture"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "BOM\nMaterial Transferred for Manufacture",
|
|
||||||
"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,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"description": "Allow multiple Material Consumption against a Work Order",
|
"description": "Allow multiple Material Consumption against a Work Order",
|
||||||
"fieldname": "material_consumption",
|
"fieldname": "material_consumption",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"label": "Allow Multiple Material Consumption"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Allow Multiple Material Consumption",
|
|
||||||
"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,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"description": "Update BOM cost automatically via Scheduler, based on latest valuation rate / price list rate / last purchase rate of raw materials.",
|
"description": "Update BOM cost automatically via Scheduler, based on latest valuation rate / price list rate / last purchase rate of raw materials.",
|
||||||
"fieldname": "update_bom_costs_automatically",
|
"fieldname": "update_bom_costs_automatically",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"label": "Update BOM Cost Automatically"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Update BOM Cost Automatically",
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_11",
|
"fieldname": "column_break_11",
|
||||||
"fieldtype": "Column Break",
|
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "default_wip_warehouse",
|
"fieldname": "default_wip_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"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": "Default Work In Progress Warehouse",
|
"label": "Default Work In Progress Warehouse",
|
||||||
"length": 0,
|
"options": "Warehouse"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Warehouse",
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "default_fg_warehouse",
|
"fieldname": "default_fg_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"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": "Default Finished Goods Warehouse",
|
"label": "Default Finished Goods Warehouse",
|
||||||
"length": 0,
|
"options": "Warehouse"
|
||||||
"no_copy": 0,
|
},
|
||||||
"options": "Warehouse",
|
{
|
||||||
"permlevel": 0,
|
"default": "0",
|
||||||
"precision": "",
|
"fieldname": "disable_capacity_planning",
|
||||||
"print_hide": 0,
|
"fieldtype": "Check",
|
||||||
"print_hide_if_no_value": 0,
|
"label": "Disable Capacity Planning"
|
||||||
"read_only": 0,
|
},
|
||||||
"remember_last_selected_value": 0,
|
{
|
||||||
"report_hide": 0,
|
"fieldname": "default_scrap_warehouse",
|
||||||
"reqd": 0,
|
"fieldtype": "Link",
|
||||||
"search_index": 0,
|
"label": "Default Scrap Warehouse",
|
||||||
"set_only_once": 0,
|
"options": "Warehouse"
|
||||||
"translatable": 0,
|
},
|
||||||
"unique": 0
|
{
|
||||||
|
"fieldname": "over_production_for_sales_and_work_order_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Over Production for Sales and Work Order"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "raw_materials_consumption_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Raw Materials Consumption"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_16",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "other_settings_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Other Settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_5",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"icon": "icon-wrench",
|
"icon": "icon-wrench",
|
||||||
"idx": 0,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"istable": 0,
|
"modified": "2019-11-26 13:10:45.569341",
|
||||||
"max_attachments": 0,
|
|
||||||
"menu_index": 0,
|
|
||||||
"modified": "2018-05-28 00:46:25.310621",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Manufacturing Settings",
|
"name": "Manufacturing Settings",
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 0,
|
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 0,
|
|
||||||
"role": "Manufacturing Manager",
|
"role": "Manufacturing Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"track_changes": 1
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
||||||
@ -5,10 +5,10 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import unittest
|
import unittest
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import flt, time_diff_in_hours, now, add_days, cint
|
from frappe.utils import flt, time_diff_in_hours, now, add_months, cint, today
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
|
||||||
from erpnext.manufacturing.doctype.work_order.work_order \
|
from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry,
|
||||||
import make_stock_entry, ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError
|
ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError, CapacityError)
|
||||||
from erpnext.stock.doctype.stock_entry import test_stock_entry
|
from erpnext.stock.doctype.stock_entry import test_stock_entry
|
||||||
from erpnext.stock.utils import get_bin
|
from erpnext.stock.utils import get_bin
|
||||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
@ -307,14 +307,50 @@ class TestWorkOrder(unittest.TestCase):
|
|||||||
{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
|
{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
|
frappe.db.set_value("Manufacturing Settings",
|
||||||
|
None, "disable_capacity_planning", 0)
|
||||||
|
|
||||||
bom, bom_item = data
|
bom, bom_item = data
|
||||||
|
|
||||||
bom_doc = frappe.get_doc('BOM', bom)
|
bom_doc = frappe.get_doc('BOM', bom)
|
||||||
work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
|
work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
|
||||||
|
self.assertTrue(work_order.planned_end_date)
|
||||||
|
|
||||||
job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name})
|
job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name})
|
||||||
self.assertEqual(len(job_cards), len(bom_doc.operations))
|
self.assertEqual(len(job_cards), len(bom_doc.operations))
|
||||||
|
|
||||||
|
def test_capcity_planning(self):
|
||||||
|
frappe.db.set_value("Manufacturing Settings", None, {
|
||||||
|
"disable_capacity_planning": 0,
|
||||||
|
"capacity_planning_for_days": 1
|
||||||
|
})
|
||||||
|
|
||||||
|
data = frappe.get_cached_value('BOM', {'docstatus': 1, 'item': '_Test FG Item 2',
|
||||||
|
'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
|
||||||
|
|
||||||
|
if data:
|
||||||
|
bom, bom_item = data
|
||||||
|
|
||||||
|
planned_start_date = add_months(today(), months=-1)
|
||||||
|
work_order = make_wo_order_test_record(item=bom_item,
|
||||||
|
qty=10, bom_no=bom, planned_start_date=planned_start_date)
|
||||||
|
|
||||||
|
work_order1 = make_wo_order_test_record(item=bom_item,
|
||||||
|
qty=30, bom_no=bom, planned_start_date=planned_start_date, do_not_submit=1)
|
||||||
|
|
||||||
|
self.assertRaises(CapacityError, work_order1.submit)
|
||||||
|
|
||||||
|
frappe.db.set_value("Manufacturing Settings", None, {
|
||||||
|
"capacity_planning_for_days": 30
|
||||||
|
})
|
||||||
|
|
||||||
|
work_order1.reload()
|
||||||
|
work_order1.submit()
|
||||||
|
self.assertTrue(work_order1.docstatus, 1)
|
||||||
|
|
||||||
|
work_order1.cancel()
|
||||||
|
work_order.cancel()
|
||||||
|
|
||||||
def test_work_order_with_non_transfer_item(self):
|
def test_work_order_with_non_transfer_item(self):
|
||||||
items = {'Finished Good Transfer Item': 1, '_Test FG Item': 1, '_Test FG Item 1': 0}
|
items = {'Finished Good Transfer Item': 1, '_Test FG Item': 1, '_Test FG Item 1': 0}
|
||||||
for item, allow_transfer in items.items():
|
for item, allow_transfer in items.items():
|
||||||
@ -371,14 +407,12 @@ def make_wo_order_test_record(**args):
|
|||||||
wo_order.skip_transfer=1
|
wo_order.skip_transfer=1
|
||||||
wo_order.get_items_and_operations_from_bom()
|
wo_order.get_items_and_operations_from_bom()
|
||||||
wo_order.sales_order = args.sales_order or None
|
wo_order.sales_order = args.sales_order or None
|
||||||
|
wo_order.planned_start_date = args.planned_start_date or now()
|
||||||
|
|
||||||
if args.source_warehouse:
|
if args.source_warehouse:
|
||||||
for item in wo_order.get("required_items"):
|
for item in wo_order.get("required_items"):
|
||||||
item.source_warehouse = args.source_warehouse
|
item.source_warehouse = args.source_warehouse
|
||||||
|
|
||||||
if args.planned_start_date:
|
|
||||||
wo_order.planned_start_date = args.planned_start_date
|
|
||||||
|
|
||||||
if not args.do_not_save:
|
if not args.do_not_save:
|
||||||
wo_order.insert()
|
wo_order.insert()
|
||||||
|
|
||||||
|
|||||||
@ -551,6 +551,7 @@ erpnext.work_order = {
|
|||||||
if (!r.exe) {
|
if (!r.exe) {
|
||||||
frm.set_value("wip_warehouse", r.message.wip_warehouse);
|
frm.set_value("wip_warehouse", r.message.wip_warehouse);
|
||||||
frm.set_value("fg_warehouse", r.message.fg_warehouse);
|
frm.set_value("fg_warehouse", r.message.fg_warehouse);
|
||||||
|
frm.set_value("scrap_warehouse", r.message.scrap_warehouse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -12,7 +12,7 @@ from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items
|
|||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
from erpnext.stock.doctype.item.item import validate_end_of_life
|
from erpnext.stock.doctype.item.item import validate_end_of_life
|
||||||
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError
|
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError
|
||||||
from erpnext.projects.doctype.timesheet.timesheet import OverlapError
|
from erpnext.manufacturing.doctype.job_card.job_card import OverlapError
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry import get_additional_costs
|
from erpnext.stock.doctype.stock_entry.stock_entry import get_additional_costs
|
||||||
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
|
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
|
||||||
from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty
|
from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty
|
||||||
@ -22,6 +22,7 @@ from erpnext.utilities.transaction_base import validate_uom_is_integer
|
|||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
|
||||||
class OverProductionError(frappe.ValidationError): pass
|
class OverProductionError(frappe.ValidationError): pass
|
||||||
|
class CapacityError(frappe.ValidationError): pass
|
||||||
class StockOverProductionError(frappe.ValidationError): pass
|
class StockOverProductionError(frappe.ValidationError): pass
|
||||||
class OperationTooLongError(frappe.ValidationError): pass
|
class OperationTooLongError(frappe.ValidationError): pass
|
||||||
class ItemHasVariantError(frappe.ValidationError): pass
|
class ItemHasVariantError(frappe.ValidationError): pass
|
||||||
@ -260,12 +261,50 @@ class WorkOrder(Document):
|
|||||||
self.update_reserved_qty_for_production()
|
self.update_reserved_qty_for_production()
|
||||||
|
|
||||||
def create_job_card(self):
|
def create_job_card(self):
|
||||||
for row in self.operations:
|
manufacturing_settings_doc = frappe.get_doc("Manufacturing Settings")
|
||||||
|
|
||||||
|
enable_capacity_planning = not cint(manufacturing_settings_doc.disable_capacity_planning)
|
||||||
|
plan_days = cint(manufacturing_settings_doc.capacity_planning_for_days) or 30
|
||||||
|
|
||||||
|
for i, row in enumerate(self.operations):
|
||||||
|
self.set_operation_start_end_time(i, row)
|
||||||
|
|
||||||
if not row.workstation:
|
if not row.workstation:
|
||||||
frappe.throw(_("Row {0}: select the workstation against the operation {1}")
|
frappe.throw(_("Row {0}: select the workstation against the operation {1}")
|
||||||
.format(row.idx, row.operation))
|
.format(row.idx, row.operation))
|
||||||
|
|
||||||
create_job_card(self, row, auto_create=True)
|
original_start_time = row.planned_start_time
|
||||||
|
job_card_doc = create_job_card(self, row,
|
||||||
|
enable_capacity_planning=enable_capacity_planning, auto_create=True)
|
||||||
|
|
||||||
|
if enable_capacity_planning and job_card_doc:
|
||||||
|
row.planned_start_time = job_card_doc.time_logs[0].from_time
|
||||||
|
row.planned_end_time = job_card_doc.time_logs[-1].to_time
|
||||||
|
|
||||||
|
if date_diff(row.planned_start_time, original_start_time) > plan_days:
|
||||||
|
frappe.throw(_("Unable to find the time slot in the next {0} days for the operation {1}.")
|
||||||
|
.format(plan_days, row.operation), CapacityError)
|
||||||
|
|
||||||
|
row.db_update()
|
||||||
|
|
||||||
|
planned_end_date = self.operations and self.operations[-1].planned_end_time
|
||||||
|
if planned_end_date:
|
||||||
|
self.db_set("planned_end_date", planned_end_date)
|
||||||
|
|
||||||
|
def set_operation_start_end_time(self, idx, row):
|
||||||
|
"""Set start and end time for given operation. If first operation, set start as
|
||||||
|
`planned_start_date`, else add time diff to end time of earlier operation."""
|
||||||
|
if idx==0:
|
||||||
|
# first operation at planned_start date
|
||||||
|
row.planned_start_time = self.planned_start_date
|
||||||
|
else:
|
||||||
|
row.planned_start_time = get_datetime(self.operations[idx-1].planned_end_time)\
|
||||||
|
+ get_mins_between_operations()
|
||||||
|
|
||||||
|
row.planned_end_time = get_datetime(row.planned_start_time) + relativedelta(minutes = row.time_in_mins)
|
||||||
|
|
||||||
|
if row.planned_start_time == row.planned_end_time:
|
||||||
|
frappe.throw(_("Capacity Planning Error, planned start time can not be same as end time"))
|
||||||
|
|
||||||
def validate_cancel(self):
|
def validate_cancel(self):
|
||||||
if self.status == "Stopped":
|
if self.status == "Stopped":
|
||||||
@ -327,9 +366,8 @@ class WorkOrder(Document):
|
|||||||
"""Fetch operations from BOM and set in 'Work Order'"""
|
"""Fetch operations from BOM and set in 'Work Order'"""
|
||||||
self.set('operations', [])
|
self.set('operations', [])
|
||||||
|
|
||||||
if not self.bom_no \
|
if not self.bom_no:
|
||||||
or cint(frappe.db.get_single_value("Manufacturing Settings", "disable_capacity_planning")):
|
return
|
||||||
return
|
|
||||||
|
|
||||||
if self.use_multi_level_bom:
|
if self.use_multi_level_bom:
|
||||||
bom_list = frappe.get_doc("BOM", self.bom_no).traverse_tree()
|
bom_list = frappe.get_doc("BOM", self.bom_no).traverse_tree()
|
||||||
@ -681,11 +719,13 @@ def make_stock_entry(work_order_id, purpose, qty=None):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_default_warehouse():
|
def get_default_warehouse():
|
||||||
wip_warehouse = frappe.db.get_single_value("Manufacturing Settings",
|
doc = frappe.get_cached_doc("Manufacturing Settings")
|
||||||
"default_wip_warehouse")
|
|
||||||
fg_warehouse = frappe.db.get_single_value("Manufacturing Settings",
|
return {
|
||||||
"default_fg_warehouse")
|
"wip_warehouse": doc.default_wip_warehouse,
|
||||||
return {"wip_warehouse": wip_warehouse, "fg_warehouse": fg_warehouse}
|
"fg_warehouse": doc.default_fg_warehouse,
|
||||||
|
"scrap_warehouse": doc.default_scrap_warehouse
|
||||||
|
}
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def stop_unstop(work_order, status):
|
def stop_unstop(work_order, status):
|
||||||
@ -721,7 +761,7 @@ def make_job_card(work_order, operation, workstation, qty=0):
|
|||||||
if row:
|
if row:
|
||||||
return create_job_card(work_order, row, qty)
|
return create_job_card(work_order, row, qty)
|
||||||
|
|
||||||
def create_job_card(work_order, row, qty=0, auto_create=False):
|
def create_job_card(work_order, row, qty=0, enable_capacity_planning=False, auto_create=False):
|
||||||
doc = frappe.new_doc("Job Card")
|
doc = frappe.new_doc("Job Card")
|
||||||
doc.update({
|
doc.update({
|
||||||
'work_order': work_order.name,
|
'work_order': work_order.name,
|
||||||
@ -741,6 +781,9 @@ def create_job_card(work_order, row, qty=0, auto_create=False):
|
|||||||
|
|
||||||
if auto_create:
|
if auto_create:
|
||||||
doc.flags.ignore_mandatory = True
|
doc.flags.ignore_mandatory = True
|
||||||
|
if enable_capacity_planning:
|
||||||
|
doc.schedule_time_logs(row)
|
||||||
|
|
||||||
doc.insert()
|
doc.insert()
|
||||||
frappe.msgprint(_("Job card {0} created").format(doc.name))
|
frappe.msgprint(_("Job card {0} created").format(doc.name))
|
||||||
|
|
||||||
|
|||||||
@ -1,466 +1,159 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:workstation_name",
|
"autoname": "field:workstation_name",
|
||||||
"beta": 0,
|
|
||||||
"creation": "2013-01-10 16:34:17",
|
"creation": "2013-01-10 16:34:17",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"editable_grid": 0,
|
"field_order": [
|
||||||
|
"workstation_name",
|
||||||
|
"production_capacity",
|
||||||
|
"column_break_3",
|
||||||
|
"over_heads",
|
||||||
|
"hour_rate_electricity",
|
||||||
|
"hour_rate_consumable",
|
||||||
|
"column_break_11",
|
||||||
|
"hour_rate_rent",
|
||||||
|
"hour_rate_labour",
|
||||||
|
"hour_rate",
|
||||||
|
"working_hours_section",
|
||||||
|
"holiday_list",
|
||||||
|
"working_hours",
|
||||||
|
"workstaion_description",
|
||||||
|
"description"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 1,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "description_section",
|
|
||||||
"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": "Description",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "workstation_name",
|
"fieldname": "workstation_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Workstation Name",
|
"label": "Workstation Name",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "workstation_name",
|
"oldfieldname": "workstation_name",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"unique": 1
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Description",
|
"label": "Description",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "description",
|
"oldfieldname": "description",
|
||||||
"oldfieldtype": "Text",
|
"oldfieldtype": "Text",
|
||||||
"permlevel": 0,
|
|
||||||
"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,
|
|
||||||
"width": "300px"
|
"width": "300px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "over_heads",
|
"fieldname": "over_heads",
|
||||||
"fieldtype": "Section Break",
|
"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": "Operating Costs",
|
"label": "Operating Costs",
|
||||||
"length": 0,
|
"oldfieldtype": "Section Break"
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldtype": "Section Break",
|
|
||||||
"permlevel": 0,
|
|
||||||
"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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"bold": 1,
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"description": "per hour",
|
"description": "per hour",
|
||||||
"fieldname": "hour_rate_electricity",
|
"fieldname": "hour_rate_electricity",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"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": "Electricity Cost",
|
"label": "Electricity Cost",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "hour_rate_electricity",
|
"oldfieldname": "hour_rate_electricity",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency"
|
||||||
"permlevel": 0,
|
|
||||||
"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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"bold": 1,
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"description": "per hour",
|
"description": "per hour",
|
||||||
"fieldname": "hour_rate_consumable",
|
"fieldname": "hour_rate_consumable",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"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": "Consumable Cost",
|
"label": "Consumable Cost",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "hour_rate_consumable",
|
"oldfieldname": "hour_rate_consumable",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency"
|
||||||
"permlevel": 0,
|
|
||||||
"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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_11",
|
"fieldname": "column_break_11",
|
||||||
"fieldtype": "Column Break",
|
"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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"bold": 1,
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"description": "per hour",
|
"description": "per hour",
|
||||||
"fieldname": "hour_rate_rent",
|
"fieldname": "hour_rate_rent",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"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": "Rent Cost",
|
"label": "Rent Cost",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "hour_rate_rent",
|
"oldfieldname": "hour_rate_rent",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency"
|
||||||
"permlevel": 0,
|
|
||||||
"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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"bold": 1,
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"description": "Wages per hour",
|
"description": "Wages per hour",
|
||||||
"fieldname": "hour_rate_labour",
|
"fieldname": "hour_rate_labour",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"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": "Wages",
|
"label": "Wages",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "hour_rate_labour",
|
"oldfieldname": "hour_rate_labour",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency"
|
||||||
"permlevel": 0,
|
|
||||||
"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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"description": "per hour",
|
"description": "per hour",
|
||||||
"fieldname": "hour_rate",
|
"fieldname": "hour_rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"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": "Net Hour Rate",
|
"label": "Net Hour Rate",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "hour_rate",
|
"oldfieldname": "hour_rate",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "working_hours_section",
|
"fieldname": "working_hours_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"label": "Working Hours"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Working Hours",
|
|
||||||
"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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "working_hours",
|
"fieldname": "working_hours",
|
||||||
"fieldtype": "Table",
|
"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": "Working Hours",
|
"label": "Working Hours",
|
||||||
"length": 0,
|
"options": "Workstation Working Hour"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Workstation Working Hour",
|
|
||||||
"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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "",
|
|
||||||
"fieldname": "holiday_list",
|
"fieldname": "holiday_list",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"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": "Holiday List",
|
"label": "Holiday List",
|
||||||
"length": 0,
|
"options": "Holiday List"
|
||||||
"no_copy": 0,
|
},
|
||||||
"options": "Holiday List",
|
{
|
||||||
"permlevel": 0,
|
"default": "1",
|
||||||
"precision": "",
|
"fieldname": "production_capacity",
|
||||||
"print_hide": 0,
|
"fieldtype": "Int",
|
||||||
"print_hide_if_no_value": 0,
|
"label": "Production Capacity",
|
||||||
"read_only": 0,
|
"reqd": 1
|
||||||
"remember_last_selected_value": 0,
|
},
|
||||||
"report_hide": 0,
|
{
|
||||||
"reqd": 0,
|
"fieldname": "column_break_3",
|
||||||
"search_index": 0,
|
"fieldtype": "Column Break"
|
||||||
"set_only_once": 0,
|
},
|
||||||
"unique": 0
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "workstaion_description",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Description"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"icon": "icon-wrench",
|
"icon": "icon-wrench",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"image_view": 0,
|
"modified": "2019-11-26 12:39:19.742052",
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2017-07-18 22:28:50.163219",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Workstation",
|
"name": "Workstation",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"apply_user_permissions": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Manufacturing User",
|
"role": "Manufacturing User",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_order": "ASC",
|
"sort_order": "ASC",
|
||||||
"track_changes": 1,
|
"track_changes": 1
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
||||||
@ -4,7 +4,9 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import flt, cint, getdate, formatdate, comma_and, time_diff_in_seconds, to_timedelta
|
from erpnext.support.doctype.issue.issue import get_holidays
|
||||||
|
from frappe.utils import (flt, cint, getdate, formatdate,
|
||||||
|
comma_and, time_diff_in_seconds, to_timedelta, add_days)
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
|
|
||||||
@ -43,6 +45,17 @@ class Workstation(Document):
|
|||||||
where parent = %s and workstation = %s""",
|
where parent = %s and workstation = %s""",
|
||||||
(self.hour_rate, bom_no[0], self.name))
|
(self.hour_rate, bom_no[0], self.name))
|
||||||
|
|
||||||
|
def validate_workstation_holiday(self, schedule_date, skip_holiday_list_check=False):
|
||||||
|
if not skip_holiday_list_check and (not self.holiday_list or
|
||||||
|
cint(frappe.db.get_single_value("Manufacturing Settings", "allow_production_on_holidays"))):
|
||||||
|
return schedule_date
|
||||||
|
|
||||||
|
if schedule_date in tuple(get_holidays(self.holiday_list)):
|
||||||
|
schedule_date = add_days(schedule_date, 1)
|
||||||
|
self.validate_workstation_holiday(schedule_date, skip_holiday_list_check=True)
|
||||||
|
|
||||||
|
return schedule_date
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_default_holiday_list():
|
def get_default_holiday_list():
|
||||||
return frappe.get_cached_value('Company', frappe.defaults.get_user_default("Company"), "default_holiday_list")
|
return frappe.get_cached_value('Company', frappe.defaults.get_user_default("Company"), "default_holiday_list")
|
||||||
|
|||||||
@ -6,8 +6,12 @@ def get_data():
|
|||||||
'fieldname': 'workstation',
|
'fieldname': 'workstation',
|
||||||
'transactions': [
|
'transactions': [
|
||||||
{
|
{
|
||||||
'label': _('Manufacture'),
|
'label': _('Master'),
|
||||||
'items': ['BOM', 'Routing', 'Work Order', 'Job Card', 'Operation', 'Timesheet']
|
'items': ['BOM', 'Routing', 'Operation']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Transaction'),
|
||||||
|
'items': ['Work Order', 'Job Card', 'Timesheet']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ def execute(filters=None):
|
|||||||
def get_data(filters, data):
|
def get_data(filters, data):
|
||||||
get_exploded_items(filters.bom, data)
|
get_exploded_items(filters.bom, data)
|
||||||
|
|
||||||
def get_exploded_items(bom, data, indent=0):
|
def get_exploded_items(bom, data, indent=0, qty=1):
|
||||||
exploded_items = frappe.get_all("BOM Item",
|
exploded_items = frappe.get_all("BOM Item",
|
||||||
filters={"parent": bom},
|
filters={"parent": bom},
|
||||||
fields= ['qty','bom_no','qty','scrap','item_code','item_name','description','uom'])
|
fields= ['qty','bom_no','qty','scrap','item_code','item_name','description','uom'])
|
||||||
@ -26,13 +26,13 @@ def get_exploded_items(bom, data, indent=0):
|
|||||||
'item_name': item.item_name,
|
'item_name': item.item_name,
|
||||||
'indent': indent,
|
'indent': indent,
|
||||||
'bom': item.bom_no,
|
'bom': item.bom_no,
|
||||||
'qty': item.qty,
|
'qty': item.qty * qty,
|
||||||
'uom': item.uom,
|
'uom': item.uom,
|
||||||
'description': item.description,
|
'description': item.description,
|
||||||
'scrap': item.scrap
|
'scrap': item.scrap
|
||||||
})
|
})
|
||||||
if item.bom_no:
|
if item.bom_no:
|
||||||
get_exploded_items(item.bom_no, data, indent=indent+1)
|
get_exploded_items(item.bom_no, data, indent=indent+1, qty=item.qty)
|
||||||
|
|
||||||
def get_columns():
|
def get_columns():
|
||||||
return [
|
return [
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.utils.data import comma_and
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
# if not filters: filters = {}
|
# if not filters: filters = {}
|
||||||
@ -13,35 +14,36 @@ def execute(filters=None):
|
|||||||
data = get_bom_stock(filters)
|
data = get_bom_stock(filters)
|
||||||
qty_to_make = filters.get("qty_to_make")
|
qty_to_make = filters.get("qty_to_make")
|
||||||
|
|
||||||
|
manufacture_details = get_manufacturer_records()
|
||||||
for row in data:
|
for row in data:
|
||||||
item_map = get_item_details(row.item_code)
|
|
||||||
reqd_qty = qty_to_make * row.actual_qty
|
reqd_qty = qty_to_make * row.actual_qty
|
||||||
last_pur_price = frappe.db.get_value("Item", row.item_code, "last_purchase_rate")
|
last_pur_price = frappe.db.get_value("Item", row.item_code, "last_purchase_rate")
|
||||||
if row.to_build > 0:
|
|
||||||
diff_qty = row.to_build - reqd_qty
|
|
||||||
summ_data.append([row.item_code, row.description, item_map[row.item_code]["manufacturer"], item_map[row.item_code]["manufacturer_part_no"], row.actual_qty, row.to_build, reqd_qty, diff_qty, last_pur_price])
|
|
||||||
else:
|
|
||||||
diff_qty = 0 - reqd_qty
|
|
||||||
summ_data.append([row.item_code, row.description, item_map[row.item_code]["manufacturer"], item_map[row.item_code]["manufacturer_part_no"], row.actual_qty, "0.000", reqd_qty, diff_qty, last_pur_price])
|
|
||||||
|
|
||||||
|
summ_data.append(get_report_data(last_pur_price, reqd_qty, row, manufacture_details))
|
||||||
return columns, summ_data
|
return columns, summ_data
|
||||||
|
|
||||||
|
def get_report_data(last_pur_price, reqd_qty, row, manufacture_details):
|
||||||
|
to_build = row.to_build if row.to_build > 0 else 0
|
||||||
|
diff_qty = to_build - reqd_qty
|
||||||
|
return [row.item_code, row.description,
|
||||||
|
comma_and(manufacture_details.get(row.item_code, {}).get('manufacturer', []), add_quotes=False),
|
||||||
|
comma_and(manufacture_details.get(row.item_code, {}).get('manufacturer_part', []), add_quotes=False),
|
||||||
|
row.actual_qty, str(to_build),
|
||||||
|
reqd_qty, diff_qty, last_pur_price]
|
||||||
|
|
||||||
def get_columns():
|
def get_columns():
|
||||||
"""return columns"""
|
"""return columns"""
|
||||||
columns = [
|
columns = [
|
||||||
_("Item") + ":Link/Item:100",
|
_("Item") + ":Link/Item:100",
|
||||||
_("Description") + "::150",
|
_("Description") + "::150",
|
||||||
_("Manufacturer") + "::100",
|
_("Manufacturer") + "::250",
|
||||||
_("Manufacturer Part Number") + "::100",
|
_("Manufacturer Part Number") + "::250",
|
||||||
_("Qty") + ":Float:50",
|
_("Qty") + ":Float:50",
|
||||||
_("Stock Qty") + ":Float:100",
|
_("Stock Qty") + ":Float:100",
|
||||||
_("Reqd Qty")+ ":Float:100",
|
_("Reqd Qty")+ ":Float:100",
|
||||||
_("Diff Qty")+ ":Float:100",
|
_("Diff Qty")+ ":Float:100",
|
||||||
_("Last Purchase Price")+ ":Float:100",
|
_("Last Purchase Price")+ ":Float:100",
|
||||||
|
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
return columns
|
return columns
|
||||||
|
|
||||||
def get_bom_stock(filters):
|
def get_bom_stock(filters):
|
||||||
@ -85,7 +87,12 @@ def get_bom_stock(filters):
|
|||||||
|
|
||||||
GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1)
|
GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1)
|
||||||
|
|
||||||
def get_item_details(item_code):
|
def get_manufacturer_records():
|
||||||
items = frappe.db.sql("""select it.item_group, it.item_name, it.stock_uom, it.name, it.brand, it.description, it.manufacturer_part_no, it.manufacturer from tabItem it where it.item_code = %s""", item_code, as_dict=1)
|
details = frappe.get_list('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no, parent"])
|
||||||
|
manufacture_details = frappe._dict()
|
||||||
|
for detail in details:
|
||||||
|
dic = manufacture_details.setdefault(detail.get('parent'), {})
|
||||||
|
dic.setdefault('manufacturer', []).append(detail.get('manufacturer'))
|
||||||
|
dic.setdefault('manufacturer_part', []).append(detail.get('manufacturer_part_no'))
|
||||||
|
|
||||||
return dict((d.name, d) for d in items)
|
return manufacture_details
|
||||||
@ -647,4 +647,5 @@ erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields
|
|||||||
erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template
|
erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template
|
||||||
erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger
|
erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger
|
||||||
erpnext.patches.v12_0.update_price_or_product_discount
|
erpnext.patches.v12_0.update_price_or_product_discount
|
||||||
|
erpnext.patches.v12_0.set_production_capacity_in_workstation
|
||||||
erpnext.patches.v12_0.set_lead_title_field
|
erpnext.patches.v12_0.set_lead_title_field
|
||||||
|
|||||||
@ -62,12 +62,12 @@ def execute():
|
|||||||
]
|
]
|
||||||
|
|
||||||
for dt in doctypes:
|
for dt in doctypes:
|
||||||
for d in frappe.db.sql("""select name, parent, item_code, item_tax_rate from `tab{0} Item`
|
for d in frappe.db.sql("""select name, parenttype, parent, item_code, item_tax_rate from `tab{0} Item`
|
||||||
where ifnull(item_tax_rate, '') not in ('', '{{}}')
|
where ifnull(item_tax_rate, '') not in ('', '{{}}')
|
||||||
and item_tax_template is NULL""".format(dt), as_dict=1):
|
and item_tax_template is NULL""".format(dt), as_dict=1):
|
||||||
item_tax_map = json.loads(d.item_tax_rate)
|
item_tax_map = json.loads(d.item_tax_rate)
|
||||||
item_tax_template_name = get_item_tax_template(item_tax_templates,
|
item_tax_template_name = get_item_tax_template(item_tax_templates,
|
||||||
item_tax_map, d.item_code, d.parent)
|
item_tax_map, d.item_code, d.parenttype, d.parent)
|
||||||
frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template_name)
|
frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template_name)
|
||||||
|
|
||||||
frappe.db.auto_commit_on_many_writes = False
|
frappe.db.auto_commit_on_many_writes = False
|
||||||
@ -77,7 +77,7 @@ def execute():
|
|||||||
settings.determine_address_tax_category_from = "Billing Address"
|
settings.determine_address_tax_category_from = "Billing Address"
|
||||||
settings.save()
|
settings.save()
|
||||||
|
|
||||||
def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parent=None):
|
def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None):
|
||||||
# search for previously created item tax template by comparing tax maps
|
# search for previously created item tax template by comparing tax maps
|
||||||
for template, item_tax_template_map in iteritems(item_tax_templates):
|
for template, item_tax_template_map in iteritems(item_tax_templates):
|
||||||
if item_tax_map == item_tax_template_map:
|
if item_tax_map == item_tax_template_map:
|
||||||
@ -88,23 +88,44 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parent=No
|
|||||||
item_tax_template.title = make_autoname("Item Tax Template-.####")
|
item_tax_template.title = make_autoname("Item Tax Template-.####")
|
||||||
|
|
||||||
for tax_type, tax_rate in iteritems(item_tax_map):
|
for tax_type, tax_rate in iteritems(item_tax_map):
|
||||||
if not frappe.db.exists("Account", tax_type):
|
account_details = frappe.db.get_value("Account", tax_type, ['name', 'account_type'], as_dict=1)
|
||||||
|
if account_details:
|
||||||
|
if account_details.account_type not in ('Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation'):
|
||||||
|
frappe.db.set_value('Account', account_details.name, 'account_type', 'Chargeable')
|
||||||
|
else:
|
||||||
parts = tax_type.strip().split(" - ")
|
parts = tax_type.strip().split(" - ")
|
||||||
account_name = " - ".join(parts[:-1])
|
account_name = " - ".join(parts[:-1])
|
||||||
company = frappe.db.get_value("Company", filters={"abbr": parts[-1]})
|
company = get_company(parts[-1], parenttype, parent)
|
||||||
parent_account = frappe.db.get_value("Account",
|
parent_account = frappe.db.get_value("Account",
|
||||||
filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0, "company": company}, fieldname="parent_account")
|
filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0, "company": company}, fieldname="parent_account")
|
||||||
|
filters = {
|
||||||
frappe.get_doc({
|
|
||||||
"doctype": "Account",
|
|
||||||
"account_name": account_name,
|
"account_name": account_name,
|
||||||
"company": company,
|
"company": company,
|
||||||
"account_type": "Tax",
|
"account_type": "Tax",
|
||||||
"parent_account": parent_account
|
"parent_account": parent_account
|
||||||
}).insert()
|
}
|
||||||
|
tax_type = frappe.db.get_value("Account", filters)
|
||||||
|
if not tax_type:
|
||||||
|
account = frappe.new_doc("Account")
|
||||||
|
account.update(filters)
|
||||||
|
account.insert()
|
||||||
|
tax_type = account.name
|
||||||
|
|
||||||
item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate})
|
item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate})
|
||||||
item_tax_templates.setdefault(item_tax_template.title, {})
|
item_tax_templates.setdefault(item_tax_template.title, {})
|
||||||
item_tax_templates[item_tax_template.title][tax_type] = tax_rate
|
item_tax_templates[item_tax_template.title][tax_type] = tax_rate
|
||||||
item_tax_template.save()
|
item_tax_template.save()
|
||||||
return item_tax_template.name
|
return item_tax_template.name
|
||||||
|
|
||||||
|
def get_company(company_abbr, parenttype=None, parent=None):
|
||||||
|
if parenttype and parent:
|
||||||
|
company = frappe.get_cached_value(parenttype, parent, 'company')
|
||||||
|
else:
|
||||||
|
company = frappe.db.get_value("Company", filters={"abbr": company_abbr})
|
||||||
|
|
||||||
|
if not company:
|
||||||
|
companies = frappe.get_all('Company')
|
||||||
|
if len(companies) == 1:
|
||||||
|
company = companies[0].name
|
||||||
|
|
||||||
|
return company
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.reload_doc("manufacturing", "doctype", "workstation")
|
||||||
|
|
||||||
|
frappe.db.sql(""" UPDATE `tabWorkstation`
|
||||||
|
SET production_capacity = 1 """)
|
||||||
@ -5,6 +5,9 @@ from frappe import _
|
|||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
"""Add setup progress actions"""
|
"""Add setup progress actions"""
|
||||||
|
if not frappe.db.exists('DocType', 'Setup Progress') or not frappe.db.exists('DocType', 'Setup Progress Action'):
|
||||||
|
return
|
||||||
|
|
||||||
frappe.reload_doc("setup", "doctype", "setup_progress")
|
frappe.reload_doc("setup", "doctype", "setup_progress")
|
||||||
frappe.reload_doc("setup", "doctype", "setup_progress_action")
|
frappe.reload_doc("setup", "doctype", "setup_progress_action")
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import unittest
|
import unittest
|
||||||
from frappe.tests.test_website import set_request
|
from frappe.utils import set_request
|
||||||
from frappe.website.render import render
|
from frappe.website.render import render
|
||||||
|
|
||||||
class TestHomepage(unittest.TestCase):
|
class TestHomepage(unittest.TestCase):
|
||||||
|
|||||||
@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
import unittest
|
import unittest
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from frappe.tests.test_website import set_request
|
from frappe.utils import set_request
|
||||||
from frappe.website.render import render
|
from frappe.website.render import render
|
||||||
|
|
||||||
class TestHomepageSection(unittest.TestCase):
|
class TestHomepageSection(unittest.TestCase):
|
||||||
|
|||||||
@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
import frappe, unittest
|
import frappe, unittest
|
||||||
from frappe.tests.test_website import set_request, get_html_for_route
|
from frappe.utils import set_request, get_html_for_route
|
||||||
from frappe.website.render import render
|
from frappe.website.render import render
|
||||||
from erpnext.portal.product_configurator.utils import get_products_for_website
|
from erpnext.portal.product_configurator.utils import get_products_for_website
|
||||||
from erpnext.stock.doctype.item.test_item import make_item_variant
|
from erpnext.stock.doctype.item.test_item import make_item_variant
|
||||||
|
|||||||
@ -313,13 +313,25 @@ def get_items(filters=None, search=None):
|
|||||||
|
|
||||||
search_condition = ''
|
search_condition = ''
|
||||||
if search:
|
if search:
|
||||||
|
# Default fields to search from
|
||||||
|
default_fields = {'name', 'item_name', 'description', 'item_group'}
|
||||||
|
|
||||||
|
# Get meta search fields
|
||||||
|
meta = frappe.get_meta("Item")
|
||||||
|
meta_fields = set(meta.get_search_fields())
|
||||||
|
|
||||||
|
# Join the meta fields and default fields set
|
||||||
|
search_fields = default_fields.union(meta_fields)
|
||||||
|
try:
|
||||||
|
if frappe.db.count('Item', cache=True) > 50000:
|
||||||
|
search_fields.remove('description')
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Build or filters for query
|
||||||
search = '%{}%'.format(search)
|
search = '%{}%'.format(search)
|
||||||
or_filters = [
|
or_filters = [[field, 'like', search] for field in search_fields]
|
||||||
['name', 'like', search],
|
|
||||||
['item_name', 'like', search],
|
|
||||||
['description', 'like', search],
|
|
||||||
['item_group', 'like', search]
|
|
||||||
]
|
|
||||||
search_condition = get_conditions(or_filters, 'or')
|
search_condition = get_conditions(or_filters, 'or')
|
||||||
|
|
||||||
filter_condition = get_conditions(filters, 'and')
|
filter_condition = get_conditions(filters, 'and')
|
||||||
|
|||||||
BIN
erpnext/public/images/illustrations/collaboration.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
erpnext/public/images/illustrations/customer.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
erpnext/public/images/illustrations/letterhead.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
erpnext/public/images/illustrations/onboard.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
erpnext/public/images/illustrations/product.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
erpnext/public/images/illustrations/supplier.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
erpnext/public/images/illustrations/user.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
@ -1716,6 +1716,14 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
against_blanket_order: function(doc, cdt, cdn) {
|
||||||
|
var item = locals[cdt][cdn];
|
||||||
|
if(!item.against_blanket_order) {
|
||||||
|
frappe.model.set_value(this.frm.doctype + " Item", item.name, "blanket_order", null);
|
||||||
|
frappe.model.set_value(this.frm.doctype + " Item", item.name, "blanket_order_rate", 0.00);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
blanket_order: function(doc, cdt, cdn) {
|
blanket_order: function(doc, cdt, cdn) {
|
||||||
var me = this;
|
var me = this;
|
||||||
var item = locals[cdt][cdn];
|
var item = locals[cdt][cdn];
|
||||||
|
|||||||
@ -3,6 +3,26 @@
|
|||||||
|
|
||||||
frappe.ui.form.on('GST HSN Code', {
|
frappe.ui.form.on('GST HSN Code', {
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
|
if(! frm.doc.__islocal && frm.doc.taxes.length){
|
||||||
|
frm.add_custom_button(__('Update Taxes for Items'), function(){
|
||||||
|
frappe.confirm(
|
||||||
|
'Are you sure? It will overwrite taxes for all items with HSN Code <b>'+frm.doc.name+'</b>.',
|
||||||
|
function(){
|
||||||
|
frappe.call({
|
||||||
|
args:{
|
||||||
|
taxes: frm.doc.taxes,
|
||||||
|
hsn_code: frm.doc.name
|
||||||
|
},
|
||||||
|
method: 'erpnext.regional.doctype.gst_hsn_code.gst_hsn_code.update_taxes_in_item_master',
|
||||||
|
callback: function(r) {
|
||||||
|
if(r.message){
|
||||||
|
frappe.show_alert(__('Item taxes updated'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1,104 +1,46 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"autoname": "field:hsn_code",
|
"autoname": "field:hsn_code",
|
||||||
"beta": 0,
|
|
||||||
"creation": "2017-06-21 10:48:56.422086",
|
"creation": "2017-06-21 10:48:56.422086",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"hsn_code",
|
||||||
|
"description",
|
||||||
|
"taxes"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "hsn_code",
|
"fieldname": "hsn_code",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "HSN Code",
|
"label": "HSN Code",
|
||||||
"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,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"unique": 1
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
"label": "Description"
|
||||||
"label": "Description",
|
},
|
||||||
"length": 0,
|
{
|
||||||
"no_copy": 0,
|
"fieldname": "taxes",
|
||||||
"permlevel": 0,
|
"fieldtype": "Table",
|
||||||
"precision": "",
|
"label": "Taxes",
|
||||||
"print_hide": 0,
|
"options": "Item Tax"
|
||||||
"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
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"modified": "2019-11-01 11:18:59.556931",
|
||||||
"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": "2017-09-29 14:38:52.220743",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Regional",
|
"module": "Regional",
|
||||||
"name": "GST HSN Code",
|
"name": "GST HSN Code",
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"search_fields": "hsn_code, description",
|
"search_fields": "hsn_code, description",
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"title_field": "hsn_code",
|
"title_field": "hsn_code",
|
||||||
"track_changes": 1,
|
"track_changes": 1
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
||||||
@ -8,3 +8,22 @@ from frappe.model.document import Document
|
|||||||
|
|
||||||
class GSTHSNCode(Document):
|
class GSTHSNCode(Document):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def update_taxes_in_item_master(taxes, hsn_code):
|
||||||
|
items = frappe.get_list("Item", filters={
|
||||||
|
'gst_hsn_code': hsn_code
|
||||||
|
})
|
||||||
|
|
||||||
|
taxes = frappe.parse_json(taxes)
|
||||||
|
frappe.enqueue(update_item_document, items=items, taxes=taxes)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def update_item_document(items, taxes):
|
||||||
|
for item in items:
|
||||||
|
item_to_be_updated=frappe.get_doc("Item", item.name)
|
||||||
|
item_to_be_updated.taxes = []
|
||||||
|
for tax in taxes:
|
||||||
|
tax = frappe._dict(tax)
|
||||||
|
item_to_be_updated.append("taxes", {'item_tax_template': tax.item_tax_template, 'tax_category': tax.tax_category})
|
||||||
|
item_to_be_updated.save()
|
||||||
@ -10,17 +10,26 @@ Provide a report and downloadable CSV according to the German DATEV format.
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
import zlib
|
||||||
|
import zipfile
|
||||||
|
import six
|
||||||
|
from six import BytesIO
|
||||||
from six import string_types
|
from six import string_types
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from .datev_constants import DataCategory
|
||||||
|
from .datev_constants import Transactions
|
||||||
|
from .datev_constants import DebtorsCreditors
|
||||||
|
from .datev_constants import AccountNames
|
||||||
|
from .datev_constants import QUERY_REPORT_COLUMNS
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
"""Entry point for frappe."""
|
"""Entry point for frappe."""
|
||||||
validate(filters)
|
validate(filters)
|
||||||
result = get_gl_entries(filters, as_dict=0)
|
result = get_transactions(filters, as_dict=0)
|
||||||
columns = get_columns()
|
columns = QUERY_REPORT_COLUMNS
|
||||||
|
|
||||||
return columns, result
|
return columns, result
|
||||||
|
|
||||||
@ -41,65 +50,8 @@ def validate(filters):
|
|||||||
except frappe.DoesNotExistError:
|
except frappe.DoesNotExistError:
|
||||||
frappe.throw(_('Please create <b>DATEV Settings</b> for Company <b>{}</b>.').format(filters.get('company')))
|
frappe.throw(_('Please create <b>DATEV Settings</b> for Company <b>{}</b>.').format(filters.get('company')))
|
||||||
|
|
||||||
def get_columns():
|
|
||||||
"""Return the list of columns that will be shown in query report."""
|
|
||||||
columns = [
|
|
||||||
{
|
|
||||||
"label": "Umsatz (ohne Soll/Haben-Kz)",
|
|
||||||
"fieldname": "Umsatz (ohne Soll/Haben-Kz)",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Soll/Haben-Kennzeichen",
|
|
||||||
"fieldname": "Soll/Haben-Kennzeichen",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Kontonummer",
|
|
||||||
"fieldname": "Kontonummer",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Gegenkonto (ohne BU-Schlüssel)",
|
|
||||||
"fieldname": "Gegenkonto (ohne BU-Schlüssel)",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Belegdatum",
|
|
||||||
"fieldname": "Belegdatum",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Buchungstext",
|
|
||||||
"fieldname": "Buchungstext",
|
|
||||||
"fieldtype": "Text",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Beleginfo - Art 1",
|
|
||||||
"fieldname": "Beleginfo - Art 1",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Beleginfo - Inhalt 1",
|
|
||||||
"fieldname": "Beleginfo - Inhalt 1",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Beleginfo - Art 2",
|
|
||||||
"fieldname": "Beleginfo - Art 2",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Beleginfo - Inhalt 2",
|
|
||||||
"fieldname": "Beleginfo - Inhalt 2",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
return columns
|
def get_transactions(filters, as_dict=1):
|
||||||
|
|
||||||
|
|
||||||
def get_gl_entries(filters, as_dict):
|
|
||||||
"""
|
"""
|
||||||
Get a list of accounting entries.
|
Get a list of accounting entries.
|
||||||
|
|
||||||
@ -111,7 +63,7 @@ def get_gl_entries(filters, as_dict):
|
|||||||
as_dict -- return as list of dicts [0,1]
|
as_dict -- return as list of dicts [0,1]
|
||||||
"""
|
"""
|
||||||
gl_entries = frappe.db.sql("""
|
gl_entries = frappe.db.sql("""
|
||||||
select
|
SELECT
|
||||||
|
|
||||||
/* either debit or credit amount; always positive */
|
/* either debit or credit amount; always positive */
|
||||||
case gl.debit when 0 then gl.credit else gl.debit end as 'Umsatz (ohne Soll/Haben-Kz)',
|
case gl.debit when 0 then gl.credit else gl.debit end as 'Umsatz (ohne Soll/Haben-Kz)',
|
||||||
@ -132,7 +84,7 @@ def get_gl_entries(filters, as_dict):
|
|||||||
gl.against_voucher_type as 'Beleginfo - Art 2',
|
gl.against_voucher_type as 'Beleginfo - Art 2',
|
||||||
gl.against_voucher as 'Beleginfo - Inhalt 2'
|
gl.against_voucher as 'Beleginfo - Inhalt 2'
|
||||||
|
|
||||||
from `tabGL Entry` gl
|
FROM `tabGL Entry` gl
|
||||||
|
|
||||||
/* Statistisches Konto (Debitoren/Kreditoren) */
|
/* Statistisches Konto (Debitoren/Kreditoren) */
|
||||||
left join `tabParty Account` pa
|
left join `tabParty Account` pa
|
||||||
@ -155,15 +107,127 @@ def get_gl_entries(filters, as_dict):
|
|||||||
left join `tabAccount` acc_against_pa
|
left join `tabAccount` acc_against_pa
|
||||||
on pa.account = acc_against_pa.name
|
on pa.account = acc_against_pa.name
|
||||||
|
|
||||||
where gl.company = %(company)s
|
WHERE gl.company = %(company)s
|
||||||
and DATE(gl.posting_date) >= %(from_date)s
|
AND DATE(gl.posting_date) >= %(from_date)s
|
||||||
and DATE(gl.posting_date) <= %(to_date)s
|
AND DATE(gl.posting_date) <= %(to_date)s
|
||||||
order by 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict)
|
ORDER BY 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict, as_utf8=1)
|
||||||
|
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|
||||||
|
|
||||||
def get_datev_csv(data, filters):
|
def get_customers(filters):
|
||||||
|
"""
|
||||||
|
Get a list of Customers.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
filters -- dict of filters to be passed to the sql query
|
||||||
|
"""
|
||||||
|
return frappe.db.sql("""
|
||||||
|
SELECT
|
||||||
|
|
||||||
|
acc.account_number as 'Konto',
|
||||||
|
cus.customer_name as 'Name (Adressatentyp Unternehmen)',
|
||||||
|
case cus.customer_type when 'Individual' then 1 when 'Company' then 2 else 0 end as 'Adressatentyp',
|
||||||
|
adr.address_line1 as 'Straße',
|
||||||
|
adr.pincode as 'Postleitzahl',
|
||||||
|
adr.city as 'Ort',
|
||||||
|
UPPER(country.code) as 'Land',
|
||||||
|
adr.address_line2 as 'Adresszusatz',
|
||||||
|
con.email_id as 'E-Mail',
|
||||||
|
coalesce(con.mobile_no, con.phone) as 'Telefon',
|
||||||
|
cus.website as 'Internet',
|
||||||
|
cus.tax_id as 'Steuernummer',
|
||||||
|
ccl.credit_limit as 'Kreditlimit (Debitor)'
|
||||||
|
|
||||||
|
FROM `tabParty Account` par
|
||||||
|
|
||||||
|
left join `tabAccount` acc
|
||||||
|
on acc.name = par.account
|
||||||
|
|
||||||
|
left join `tabCustomer` cus
|
||||||
|
on cus.name = par.parent
|
||||||
|
|
||||||
|
left join `tabAddress` adr
|
||||||
|
on adr.name = cus.customer_primary_address
|
||||||
|
|
||||||
|
left join `tabCountry` country
|
||||||
|
on country.name = adr.country
|
||||||
|
|
||||||
|
left join `tabContact` con
|
||||||
|
on con.name = cus.customer_primary_contact
|
||||||
|
|
||||||
|
left join `tabCustomer Credit Limit` ccl
|
||||||
|
on ccl.parent = cus.name
|
||||||
|
and ccl.company = par.company
|
||||||
|
|
||||||
|
WHERE par.company = %(company)s
|
||||||
|
AND par.parenttype = 'Customer'""", filters, as_dict=1, as_utf8=1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_suppliers(filters):
|
||||||
|
"""
|
||||||
|
Get a list of Suppliers.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
filters -- dict of filters to be passed to the sql query
|
||||||
|
"""
|
||||||
|
return frappe.db.sql("""
|
||||||
|
SELECT
|
||||||
|
|
||||||
|
acc.account_number as 'Konto',
|
||||||
|
sup.supplier_name as 'Name (Adressatentyp Unternehmen)',
|
||||||
|
case sup.supplier_type when 'Individual' then '1' when 'Company' then '2' else '0' end as 'Adressatentyp',
|
||||||
|
adr.address_line1 as 'Straße',
|
||||||
|
adr.pincode as 'Postleitzahl',
|
||||||
|
adr.city as 'Ort',
|
||||||
|
UPPER(country.code) as 'Land',
|
||||||
|
adr.address_line2 as 'Adresszusatz',
|
||||||
|
con.email_id as 'E-Mail',
|
||||||
|
coalesce(con.mobile_no, con.phone) as 'Telefon',
|
||||||
|
sup.website as 'Internet',
|
||||||
|
sup.tax_id as 'Steuernummer',
|
||||||
|
case sup.on_hold when 1 then sup.release_date else null end as 'Zahlungssperre bis'
|
||||||
|
|
||||||
|
FROM `tabParty Account` par
|
||||||
|
|
||||||
|
left join `tabAccount` acc
|
||||||
|
on acc.name = par.account
|
||||||
|
|
||||||
|
left join `tabSupplier` sup
|
||||||
|
on sup.name = par.parent
|
||||||
|
|
||||||
|
left join `tabDynamic Link` dyn_adr
|
||||||
|
on dyn_adr.link_name = sup.name
|
||||||
|
and dyn_adr.link_doctype = 'Supplier'
|
||||||
|
and dyn_adr.parenttype = 'Address'
|
||||||
|
|
||||||
|
left join `tabAddress` adr
|
||||||
|
on adr.name = dyn_adr.parent
|
||||||
|
and adr.is_primary_address = '1'
|
||||||
|
|
||||||
|
left join `tabCountry` country
|
||||||
|
on country.name = adr.country
|
||||||
|
|
||||||
|
left join `tabDynamic Link` dyn_con
|
||||||
|
on dyn_con.link_name = sup.name
|
||||||
|
and dyn_con.link_doctype = 'Supplier'
|
||||||
|
and dyn_con.parenttype = 'Contact'
|
||||||
|
|
||||||
|
left join `tabContact` con
|
||||||
|
on con.name = dyn_con.parent
|
||||||
|
and con.is_primary_contact = '1'
|
||||||
|
|
||||||
|
WHERE par.company = %(company)s
|
||||||
|
AND par.parenttype = 'Supplier'""", filters, as_dict=1, as_utf8=1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_account_names(filters):
|
||||||
|
return frappe.get_list("Account",
|
||||||
|
fields=["account_number as Konto", "name as Kontenbeschriftung"],
|
||||||
|
filters={"company": filters.get("company"), "is_group": "0"})
|
||||||
|
|
||||||
|
|
||||||
|
def get_datev_csv(data, filters, csv_class):
|
||||||
"""
|
"""
|
||||||
Fill in missing columns and return a CSV in DATEV Format.
|
Fill in missing columns and return a CSV in DATEV Format.
|
||||||
|
|
||||||
@ -174,7 +238,46 @@ def get_datev_csv(data, filters):
|
|||||||
Arguments:
|
Arguments:
|
||||||
data -- array of dictionaries
|
data -- array of dictionaries
|
||||||
filters -- dict
|
filters -- dict
|
||||||
|
csv_class -- defines DATA_CATEGORY, FORMAT_NAME and COLUMNS
|
||||||
"""
|
"""
|
||||||
|
header = get_header(filters, csv_class)
|
||||||
|
|
||||||
|
empty_df = pd.DataFrame(columns=csv_class.COLUMNS)
|
||||||
|
data_df = pd.DataFrame.from_records(data)
|
||||||
|
|
||||||
|
result = empty_df.append(data_df, sort=True)
|
||||||
|
|
||||||
|
if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS:
|
||||||
|
result['Belegdatum'] = pd.to_datetime(result['Belegdatum'])
|
||||||
|
|
||||||
|
if csv_class.DATA_CATEGORY == DataCategory.ACCOUNT_NAMES:
|
||||||
|
result['Sprach-ID'] = 'de-DE'
|
||||||
|
|
||||||
|
header = ';'.join(header).encode('latin_1')
|
||||||
|
data = result.to_csv(
|
||||||
|
# Reason for str(';'): https://github.com/pandas-dev/pandas/issues/6035
|
||||||
|
sep=str(';'),
|
||||||
|
# European decimal seperator
|
||||||
|
decimal=',',
|
||||||
|
# Windows "ANSI" encoding
|
||||||
|
encoding='latin_1',
|
||||||
|
# format date as DDMM
|
||||||
|
date_format='%d%m',
|
||||||
|
# Windows line terminator
|
||||||
|
line_terminator='\r\n',
|
||||||
|
# Do not number rows
|
||||||
|
index=False,
|
||||||
|
# Use all columns defined above
|
||||||
|
columns=csv_class.COLUMNS
|
||||||
|
)
|
||||||
|
|
||||||
|
if not six.PY2:
|
||||||
|
data = data.encode('latin_1')
|
||||||
|
|
||||||
|
return header + b'\r\n' + data
|
||||||
|
|
||||||
|
|
||||||
|
def get_header(filters, csv_class):
|
||||||
header = [
|
header = [
|
||||||
# A = DATEV format
|
# A = DATEV format
|
||||||
# DTVF = created by DATEV software,
|
# DTVF = created by DATEV software,
|
||||||
@ -185,18 +288,8 @@ def get_datev_csv(data, filters):
|
|||||||
# 510 = 5.10,
|
# 510 = 5.10,
|
||||||
# 720 = 7.20
|
# 720 = 7.20
|
||||||
"510",
|
"510",
|
||||||
# C = Data category
|
csv_class.DATA_CATEGORY,
|
||||||
# 21 = Transaction batch (Buchungsstapel),
|
csv_class.FORMAT_NAME,
|
||||||
# 67 = Buchungstextkonstanten,
|
|
||||||
# 16 = Debitors/Creditors,
|
|
||||||
# 20 = Account names (Kontenbeschriftungen)
|
|
||||||
"21",
|
|
||||||
# D = Format name
|
|
||||||
# Buchungsstapel,
|
|
||||||
# Buchungstextkonstanten,
|
|
||||||
# Debitoren/Kreditoren,
|
|
||||||
# Kontenbeschriftungen
|
|
||||||
"Buchungsstapel",
|
|
||||||
# E = Format version (regarding format name)
|
# E = Format version (regarding format name)
|
||||||
"",
|
"",
|
||||||
# F = Generated on
|
# F = Generated on
|
||||||
@ -224,16 +317,17 @@ def get_datev_csv(data, filters):
|
|||||||
# P = Transaction batch end date (YYYYMMDD)
|
# P = Transaction batch end date (YYYYMMDD)
|
||||||
frappe.utils.formatdate(filters.get('to_date'), "yyyyMMdd"),
|
frappe.utils.formatdate(filters.get('to_date'), "yyyyMMdd"),
|
||||||
# Q = Description (for example, "January - February 2019 Transactions")
|
# Q = Description (for example, "January - February 2019 Transactions")
|
||||||
"{} - {} Buchungsstapel".format(
|
"{} - {} {}".format(
|
||||||
frappe.utils.formatdate(filters.get('from_date'), "MMMM yyyy"),
|
frappe.utils.formatdate(filters.get('from_date'), "MMMM yyyy"),
|
||||||
frappe.utils.formatdate(filters.get('to_date'), "MMMM yyyy")
|
frappe.utils.formatdate(filters.get('to_date'), "MMMM yyyy"),
|
||||||
|
csv_class.FORMAT_NAME
|
||||||
),
|
),
|
||||||
# R = Diktatkürzel
|
# R = Diktatkürzel
|
||||||
"",
|
"",
|
||||||
# S = Buchungstyp
|
# S = Buchungstyp
|
||||||
# 1 = Transaction batch (Buchungsstapel),
|
# 1 = Transaction batch (Buchungsstapel),
|
||||||
# 2 = Annual financial statement (Jahresabschluss)
|
# 2 = Annual financial statement (Jahresabschluss)
|
||||||
"1",
|
"1" if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else "",
|
||||||
# T = Rechnungslegungszweck
|
# T = Rechnungslegungszweck
|
||||||
"",
|
"",
|
||||||
# U = Festschreibung
|
# U = Festschreibung
|
||||||
@ -241,185 +335,8 @@ def get_datev_csv(data, filters):
|
|||||||
# V = Kontoführungs-Währungskennzeichen des Geldkontos
|
# V = Kontoführungs-Währungskennzeichen des Geldkontos
|
||||||
frappe.get_value("Company", filters.get("company"), "default_currency")
|
frappe.get_value("Company", filters.get("company"), "default_currency")
|
||||||
]
|
]
|
||||||
columns = [
|
return header
|
||||||
# All possible columns must tbe listed here, because DATEV requires them to
|
|
||||||
# be present in the CSV.
|
|
||||||
# ---
|
|
||||||
# Umsatz
|
|
||||||
"Umsatz (ohne Soll/Haben-Kz)",
|
|
||||||
"Soll/Haben-Kennzeichen",
|
|
||||||
"WKZ Umsatz",
|
|
||||||
"Kurs",
|
|
||||||
"Basis-Umsatz",
|
|
||||||
"WKZ Basis-Umsatz",
|
|
||||||
# Konto/Gegenkonto
|
|
||||||
"Kontonummer",
|
|
||||||
"Gegenkonto (ohne BU-Schlüssel)",
|
|
||||||
"BU-Schlüssel",
|
|
||||||
# Datum
|
|
||||||
"Belegdatum",
|
|
||||||
# Belegfelder
|
|
||||||
"Belegfeld 1",
|
|
||||||
"Belegfeld 2",
|
|
||||||
# Weitere Felder
|
|
||||||
"Skonto",
|
|
||||||
"Buchungstext",
|
|
||||||
# OPOS-Informationen
|
|
||||||
"Postensperre",
|
|
||||||
"Diverse Adressnummer",
|
|
||||||
"Geschäftspartnerbank",
|
|
||||||
"Sachverhalt",
|
|
||||||
"Zinssperre",
|
|
||||||
# Digitaler Beleg
|
|
||||||
"Beleglink",
|
|
||||||
# Beleginfo
|
|
||||||
"Beleginfo - Art 1",
|
|
||||||
"Beleginfo - Inhalt 1",
|
|
||||||
"Beleginfo - Art 2",
|
|
||||||
"Beleginfo - Inhalt 2",
|
|
||||||
"Beleginfo - Art 3",
|
|
||||||
"Beleginfo - Inhalt 3",
|
|
||||||
"Beleginfo - Art 4",
|
|
||||||
"Beleginfo - Inhalt 4",
|
|
||||||
"Beleginfo - Art 5",
|
|
||||||
"Beleginfo - Inhalt 5",
|
|
||||||
"Beleginfo - Art 6",
|
|
||||||
"Beleginfo - Inhalt 6",
|
|
||||||
"Beleginfo - Art 7",
|
|
||||||
"Beleginfo - Inhalt 7",
|
|
||||||
"Beleginfo - Art 8",
|
|
||||||
"Beleginfo - Inhalt 8",
|
|
||||||
# Kostenrechnung
|
|
||||||
"Kost 1 - Kostenstelle",
|
|
||||||
"Kost 2 - Kostenstelle",
|
|
||||||
"Kost-Menge",
|
|
||||||
# Steuerrechnung
|
|
||||||
"EU-Land u. UStID",
|
|
||||||
"EU-Steuersatz",
|
|
||||||
"Abw. Versteuerungsart",
|
|
||||||
# L+L Sachverhalt
|
|
||||||
"Sachverhalt L+L",
|
|
||||||
"Funktionsergänzung L+L",
|
|
||||||
# Funktion Steuerschlüssel 49
|
|
||||||
"BU 49 Hauptfunktionstyp",
|
|
||||||
"BU 49 Hauptfunktionsnummer",
|
|
||||||
"BU 49 Funktionsergänzung",
|
|
||||||
# Zusatzinformationen
|
|
||||||
"Zusatzinformation - Art 1",
|
|
||||||
"Zusatzinformation - Inhalt 1",
|
|
||||||
"Zusatzinformation - Art 2",
|
|
||||||
"Zusatzinformation - Inhalt 2",
|
|
||||||
"Zusatzinformation - Art 3",
|
|
||||||
"Zusatzinformation - Inhalt 3",
|
|
||||||
"Zusatzinformation - Art 4",
|
|
||||||
"Zusatzinformation - Inhalt 4",
|
|
||||||
"Zusatzinformation - Art 5",
|
|
||||||
"Zusatzinformation - Inhalt 5",
|
|
||||||
"Zusatzinformation - Art 6",
|
|
||||||
"Zusatzinformation - Inhalt 6",
|
|
||||||
"Zusatzinformation - Art 7",
|
|
||||||
"Zusatzinformation - Inhalt 7",
|
|
||||||
"Zusatzinformation - Art 8",
|
|
||||||
"Zusatzinformation - Inhalt 8",
|
|
||||||
"Zusatzinformation - Art 9",
|
|
||||||
"Zusatzinformation - Inhalt 9",
|
|
||||||
"Zusatzinformation - Art 10",
|
|
||||||
"Zusatzinformation - Inhalt 10",
|
|
||||||
"Zusatzinformation - Art 11",
|
|
||||||
"Zusatzinformation - Inhalt 11",
|
|
||||||
"Zusatzinformation - Art 12",
|
|
||||||
"Zusatzinformation - Inhalt 12",
|
|
||||||
"Zusatzinformation - Art 13",
|
|
||||||
"Zusatzinformation - Inhalt 13",
|
|
||||||
"Zusatzinformation - Art 14",
|
|
||||||
"Zusatzinformation - Inhalt 14",
|
|
||||||
"Zusatzinformation - Art 15",
|
|
||||||
"Zusatzinformation - Inhalt 15",
|
|
||||||
"Zusatzinformation - Art 16",
|
|
||||||
"Zusatzinformation - Inhalt 16",
|
|
||||||
"Zusatzinformation - Art 17",
|
|
||||||
"Zusatzinformation - Inhalt 17",
|
|
||||||
"Zusatzinformation - Art 18",
|
|
||||||
"Zusatzinformation - Inhalt 18",
|
|
||||||
"Zusatzinformation - Art 19",
|
|
||||||
"Zusatzinformation - Inhalt 19",
|
|
||||||
"Zusatzinformation - Art 20",
|
|
||||||
"Zusatzinformation - Inhalt 20",
|
|
||||||
# Mengenfelder LuF
|
|
||||||
"Stück",
|
|
||||||
"Gewicht",
|
|
||||||
# Forderungsart
|
|
||||||
"Zahlweise",
|
|
||||||
"Forderungsart",
|
|
||||||
"Veranlagungsjahr",
|
|
||||||
"Zugeordnete Fälligkeit",
|
|
||||||
# Weitere Felder
|
|
||||||
"Skontotyp",
|
|
||||||
# Anzahlungen
|
|
||||||
"Auftragsnummer",
|
|
||||||
"Buchungstyp",
|
|
||||||
"USt-Schlüssel (Anzahlungen)",
|
|
||||||
"EU-Land (Anzahlungen)",
|
|
||||||
"Sachverhalt L+L (Anzahlungen)",
|
|
||||||
"EU-Steuersatz (Anzahlungen)",
|
|
||||||
"Erlöskonto (Anzahlungen)",
|
|
||||||
# Stapelinformationen
|
|
||||||
"Herkunft-Kz",
|
|
||||||
# Technische Identifikation
|
|
||||||
"Buchungs GUID",
|
|
||||||
# Kostenrechnung
|
|
||||||
"Kost-Datum",
|
|
||||||
# OPOS-Informationen
|
|
||||||
"SEPA-Mandatsreferenz",
|
|
||||||
"Skontosperre",
|
|
||||||
# Gesellschafter und Sonderbilanzsachverhalt
|
|
||||||
"Gesellschaftername",
|
|
||||||
"Beteiligtennummer",
|
|
||||||
"Identifikationsnummer",
|
|
||||||
"Zeichnernummer",
|
|
||||||
# OPOS-Informationen
|
|
||||||
"Postensperre bis",
|
|
||||||
# Gesellschafter und Sonderbilanzsachverhalt
|
|
||||||
"Bezeichnung SoBil-Sachverhalt",
|
|
||||||
"Kennzeichen SoBil-Buchung",
|
|
||||||
# Stapelinformationen
|
|
||||||
"Festschreibung",
|
|
||||||
# Datum
|
|
||||||
"Leistungsdatum",
|
|
||||||
"Datum Zuord. Steuerperiode",
|
|
||||||
# OPOS-Informationen
|
|
||||||
"Fälligkeit",
|
|
||||||
# Konto/Gegenkonto
|
|
||||||
"Generalumkehr (GU)",
|
|
||||||
# Steuersatz für Steuerschlüssel
|
|
||||||
"Steuersatz",
|
|
||||||
"Land"
|
|
||||||
]
|
|
||||||
|
|
||||||
empty_df = pd.DataFrame(columns=columns)
|
|
||||||
data_df = pd.DataFrame.from_records(data)
|
|
||||||
|
|
||||||
result = empty_df.append(data_df)
|
|
||||||
result['Belegdatum'] = pd.to_datetime(result['Belegdatum'])
|
|
||||||
|
|
||||||
header = ';'.join(header).encode('latin_1')
|
|
||||||
data = result.to_csv(
|
|
||||||
sep=b';',
|
|
||||||
# European decimal seperator
|
|
||||||
decimal=',',
|
|
||||||
# Windows "ANSI" encoding
|
|
||||||
encoding='latin_1',
|
|
||||||
# format date as DDMM
|
|
||||||
date_format='%d%m',
|
|
||||||
# Windows line terminator
|
|
||||||
line_terminator=b'\r\n',
|
|
||||||
# Do not number rows
|
|
||||||
index=False,
|
|
||||||
# Use all columns defined above
|
|
||||||
columns=columns
|
|
||||||
)
|
|
||||||
|
|
||||||
return header + b'\r\n' + data
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def download_datev_csv(filters=None):
|
def download_datev_csv(filters=None):
|
||||||
@ -438,8 +355,31 @@ def download_datev_csv(filters=None):
|
|||||||
filters = json.loads(filters)
|
filters = json.loads(filters)
|
||||||
|
|
||||||
validate(filters)
|
validate(filters)
|
||||||
data = get_gl_entries(filters, as_dict=1)
|
|
||||||
|
|
||||||
frappe.response['result'] = get_datev_csv(data, filters)
|
# This is where my zip will be written
|
||||||
frappe.response['doctype'] = 'EXTF_Buchungsstapel'
|
zip_buffer = BytesIO()
|
||||||
frappe.response['type'] = 'csv'
|
# This is my zip file
|
||||||
|
datev_zip = zipfile.ZipFile(zip_buffer, mode='w', compression=zipfile.ZIP_DEFLATED)
|
||||||
|
|
||||||
|
transactions = get_transactions(filters)
|
||||||
|
transactions_csv = get_datev_csv(transactions, filters, csv_class=Transactions)
|
||||||
|
datev_zip.writestr('EXTF_Buchungsstapel.csv', transactions_csv)
|
||||||
|
|
||||||
|
account_names = get_account_names(filters)
|
||||||
|
account_names_csv = get_datev_csv(account_names, filters, csv_class=AccountNames)
|
||||||
|
datev_zip.writestr('EXTF_Kontenbeschriftungen.csv', account_names_csv)
|
||||||
|
|
||||||
|
customers = get_customers(filters)
|
||||||
|
customers_csv = get_datev_csv(customers, filters, csv_class=DebtorsCreditors)
|
||||||
|
datev_zip.writestr('EXTF_Kunden.csv', customers_csv)
|
||||||
|
|
||||||
|
suppliers = get_suppliers(filters)
|
||||||
|
suppliers_csv = get_datev_csv(suppliers, filters, csv_class=DebtorsCreditors)
|
||||||
|
datev_zip.writestr('EXTF_Lieferanten.csv', suppliers_csv)
|
||||||
|
|
||||||
|
# You must call close() before exiting your program or essential records will not be written.
|
||||||
|
datev_zip.close()
|
||||||
|
|
||||||
|
frappe.response['filecontent'] = zip_buffer.getvalue()
|
||||||
|
frappe.response['filename'] = 'DATEV.zip'
|
||||||
|
frappe.response['type'] = 'binary'
|
||||||
|
|||||||
512
erpnext/regional/report/datev/datev_constants.py
Normal file
@ -0,0 +1,512 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
"""Constants used in datev.py."""
|
||||||
|
|
||||||
|
TRANSACTION_COLUMNS = [
|
||||||
|
# All possible columns must tbe listed here, because DATEV requires them to
|
||||||
|
# be present in the CSV.
|
||||||
|
# ---
|
||||||
|
# Umsatz
|
||||||
|
"Umsatz (ohne Soll/Haben-Kz)",
|
||||||
|
"Soll/Haben-Kennzeichen",
|
||||||
|
"WKZ Umsatz",
|
||||||
|
"Kurs",
|
||||||
|
"Basis-Umsatz",
|
||||||
|
"WKZ Basis-Umsatz",
|
||||||
|
# Konto/Gegenkonto
|
||||||
|
"Kontonummer",
|
||||||
|
"Gegenkonto (ohne BU-Schlüssel)",
|
||||||
|
"BU-Schlüssel",
|
||||||
|
# Datum
|
||||||
|
"Belegdatum",
|
||||||
|
# Belegfelder
|
||||||
|
"Belegfeld 1",
|
||||||
|
"Belegfeld 2",
|
||||||
|
# Weitere Felder
|
||||||
|
"Skonto",
|
||||||
|
"Buchungstext",
|
||||||
|
# OPOS-Informationen
|
||||||
|
"Postensperre",
|
||||||
|
"Diverse Adressnummer",
|
||||||
|
"Geschäftspartnerbank",
|
||||||
|
"Sachverhalt",
|
||||||
|
"Zinssperre",
|
||||||
|
# Digitaler Beleg
|
||||||
|
"Beleglink",
|
||||||
|
# Beleginfo
|
||||||
|
"Beleginfo - Art 1",
|
||||||
|
"Beleginfo - Inhalt 1",
|
||||||
|
"Beleginfo - Art 2",
|
||||||
|
"Beleginfo - Inhalt 2",
|
||||||
|
"Beleginfo - Art 3",
|
||||||
|
"Beleginfo - Inhalt 3",
|
||||||
|
"Beleginfo - Art 4",
|
||||||
|
"Beleginfo - Inhalt 4",
|
||||||
|
"Beleginfo - Art 5",
|
||||||
|
"Beleginfo - Inhalt 5",
|
||||||
|
"Beleginfo - Art 6",
|
||||||
|
"Beleginfo - Inhalt 6",
|
||||||
|
"Beleginfo - Art 7",
|
||||||
|
"Beleginfo - Inhalt 7",
|
||||||
|
"Beleginfo - Art 8",
|
||||||
|
"Beleginfo - Inhalt 8",
|
||||||
|
# Kostenrechnung
|
||||||
|
"Kost 1 - Kostenstelle",
|
||||||
|
"Kost 2 - Kostenstelle",
|
||||||
|
"Kost-Menge",
|
||||||
|
# Steuerrechnung
|
||||||
|
"EU-Land u. UStID",
|
||||||
|
"EU-Steuersatz",
|
||||||
|
"Abw. Versteuerungsart",
|
||||||
|
# L+L Sachverhalt
|
||||||
|
"Sachverhalt L+L",
|
||||||
|
"Funktionsergänzung L+L",
|
||||||
|
# Funktion Steuerschlüssel 49
|
||||||
|
"BU 49 Hauptfunktionstyp",
|
||||||
|
"BU 49 Hauptfunktionsnummer",
|
||||||
|
"BU 49 Funktionsergänzung",
|
||||||
|
# Zusatzinformationen
|
||||||
|
"Zusatzinformation - Art 1",
|
||||||
|
"Zusatzinformation - Inhalt 1",
|
||||||
|
"Zusatzinformation - Art 2",
|
||||||
|
"Zusatzinformation - Inhalt 2",
|
||||||
|
"Zusatzinformation - Art 3",
|
||||||
|
"Zusatzinformation - Inhalt 3",
|
||||||
|
"Zusatzinformation - Art 4",
|
||||||
|
"Zusatzinformation - Inhalt 4",
|
||||||
|
"Zusatzinformation - Art 5",
|
||||||
|
"Zusatzinformation - Inhalt 5",
|
||||||
|
"Zusatzinformation - Art 6",
|
||||||
|
"Zusatzinformation - Inhalt 6",
|
||||||
|
"Zusatzinformation - Art 7",
|
||||||
|
"Zusatzinformation - Inhalt 7",
|
||||||
|
"Zusatzinformation - Art 8",
|
||||||
|
"Zusatzinformation - Inhalt 8",
|
||||||
|
"Zusatzinformation - Art 9",
|
||||||
|
"Zusatzinformation - Inhalt 9",
|
||||||
|
"Zusatzinformation - Art 10",
|
||||||
|
"Zusatzinformation - Inhalt 10",
|
||||||
|
"Zusatzinformation - Art 11",
|
||||||
|
"Zusatzinformation - Inhalt 11",
|
||||||
|
"Zusatzinformation - Art 12",
|
||||||
|
"Zusatzinformation - Inhalt 12",
|
||||||
|
"Zusatzinformation - Art 13",
|
||||||
|
"Zusatzinformation - Inhalt 13",
|
||||||
|
"Zusatzinformation - Art 14",
|
||||||
|
"Zusatzinformation - Inhalt 14",
|
||||||
|
"Zusatzinformation - Art 15",
|
||||||
|
"Zusatzinformation - Inhalt 15",
|
||||||
|
"Zusatzinformation - Art 16",
|
||||||
|
"Zusatzinformation - Inhalt 16",
|
||||||
|
"Zusatzinformation - Art 17",
|
||||||
|
"Zusatzinformation - Inhalt 17",
|
||||||
|
"Zusatzinformation - Art 18",
|
||||||
|
"Zusatzinformation - Inhalt 18",
|
||||||
|
"Zusatzinformation - Art 19",
|
||||||
|
"Zusatzinformation - Inhalt 19",
|
||||||
|
"Zusatzinformation - Art 20",
|
||||||
|
"Zusatzinformation - Inhalt 20",
|
||||||
|
# Mengenfelder LuF
|
||||||
|
"Stück",
|
||||||
|
"Gewicht",
|
||||||
|
# Forderungsart
|
||||||
|
"Zahlweise",
|
||||||
|
"Forderungsart",
|
||||||
|
"Veranlagungsjahr",
|
||||||
|
"Zugeordnete Fälligkeit",
|
||||||
|
# Weitere Felder
|
||||||
|
"Skontotyp",
|
||||||
|
# Anzahlungen
|
||||||
|
"Auftragsnummer",
|
||||||
|
"Buchungstyp",
|
||||||
|
"USt-Schlüssel (Anzahlungen)",
|
||||||
|
"EU-Land (Anzahlungen)",
|
||||||
|
"Sachverhalt L+L (Anzahlungen)",
|
||||||
|
"EU-Steuersatz (Anzahlungen)",
|
||||||
|
"Erlöskonto (Anzahlungen)",
|
||||||
|
# Stapelinformationen
|
||||||
|
"Herkunft-Kz",
|
||||||
|
# Technische Identifikation
|
||||||
|
"Buchungs GUID",
|
||||||
|
# Kostenrechnung
|
||||||
|
"Kost-Datum",
|
||||||
|
# OPOS-Informationen
|
||||||
|
"SEPA-Mandatsreferenz",
|
||||||
|
"Skontosperre",
|
||||||
|
# Gesellschafter und Sonderbilanzsachverhalt
|
||||||
|
"Gesellschaftername",
|
||||||
|
"Beteiligtennummer",
|
||||||
|
"Identifikationsnummer",
|
||||||
|
"Zeichnernummer",
|
||||||
|
# OPOS-Informationen
|
||||||
|
"Postensperre bis",
|
||||||
|
# Gesellschafter und Sonderbilanzsachverhalt
|
||||||
|
"Bezeichnung SoBil-Sachverhalt",
|
||||||
|
"Kennzeichen SoBil-Buchung",
|
||||||
|
# Stapelinformationen
|
||||||
|
"Festschreibung",
|
||||||
|
# Datum
|
||||||
|
"Leistungsdatum",
|
||||||
|
"Datum Zuord. Steuerperiode",
|
||||||
|
# OPOS-Informationen
|
||||||
|
"Fälligkeit",
|
||||||
|
# Konto/Gegenkonto
|
||||||
|
"Generalumkehr (GU)",
|
||||||
|
# Steuersatz für Steuerschlüssel
|
||||||
|
"Steuersatz",
|
||||||
|
"Land"
|
||||||
|
]
|
||||||
|
|
||||||
|
DEBTOR_CREDITOR_COLUMNS = [
|
||||||
|
# All possible columns must tbe listed here, because DATEV requires them to
|
||||||
|
# be present in the CSV.
|
||||||
|
# Columns "Leerfeld" have been replaced with "Leerfeld #" to not confuse pandas
|
||||||
|
# ---
|
||||||
|
"Konto",
|
||||||
|
"Name (Adressatentyp Unternehmen)",
|
||||||
|
"Unternehmensgegenstand",
|
||||||
|
"Name (Adressatentyp natürl. Person)",
|
||||||
|
"Vorname (Adressatentyp natürl. Person)",
|
||||||
|
"Name (Adressatentyp keine Angabe)",
|
||||||
|
"Adressatentyp",
|
||||||
|
"Kurzbezeichnung",
|
||||||
|
"EU-Land",
|
||||||
|
"EU-USt-IdNr.",
|
||||||
|
"Anrede",
|
||||||
|
"Titel/Akad. Grad",
|
||||||
|
"Adelstitel",
|
||||||
|
"Namensvorsatz",
|
||||||
|
"Adressart",
|
||||||
|
"Straße",
|
||||||
|
"Postfach",
|
||||||
|
"Postleitzahl",
|
||||||
|
"Ort",
|
||||||
|
"Land",
|
||||||
|
"Versandzusatz",
|
||||||
|
"Adresszusatz",
|
||||||
|
"Abweichende Anrede",
|
||||||
|
"Abw. Zustellbezeichnung 1",
|
||||||
|
"Abw. Zustellbezeichnung 2",
|
||||||
|
"Kennz. Korrespondenzadresse",
|
||||||
|
"Adresse gültig von",
|
||||||
|
"Adresse gültig bis",
|
||||||
|
"Telefon",
|
||||||
|
"Bemerkung (Telefon)",
|
||||||
|
"Telefon Geschäftsleitung",
|
||||||
|
"Bemerkung (Telefon GL)",
|
||||||
|
"E-Mail",
|
||||||
|
"Bemerkung (E-Mail)",
|
||||||
|
"Internet",
|
||||||
|
"Bemerkung (Internet)",
|
||||||
|
"Fax",
|
||||||
|
"Bemerkung (Fax)",
|
||||||
|
"Sonstige",
|
||||||
|
"Bemerkung (Sonstige)",
|
||||||
|
"Bankleitzahl 1",
|
||||||
|
"Bankbezeichnung 1",
|
||||||
|
"Bankkonto-Nummer 1",
|
||||||
|
"Länderkennzeichen 1",
|
||||||
|
"IBAN 1",
|
||||||
|
"Leerfeld 1",
|
||||||
|
"SWIFT-Code 1",
|
||||||
|
"Abw. Kontoinhaber 1",
|
||||||
|
"Kennz. Haupt-Bankverb. 1",
|
||||||
|
"Bankverb. 1 Gültig von",
|
||||||
|
"Bankverb. 1 Gültig bis",
|
||||||
|
"Bankleitzahl 2",
|
||||||
|
"Bankbezeichnung 2",
|
||||||
|
"Bankkonto-Nummer 2",
|
||||||
|
"Länderkennzeichen 2",
|
||||||
|
"IBAN 2",
|
||||||
|
"Leerfeld 2",
|
||||||
|
"SWIFT-Code 2",
|
||||||
|
"Abw. Kontoinhaber 2",
|
||||||
|
"Kennz. Haupt-Bankverb. 2",
|
||||||
|
"Bankverb. 2 gültig von",
|
||||||
|
"Bankverb. 2 gültig bis",
|
||||||
|
"Bankleitzahl 3",
|
||||||
|
"Bankbezeichnung 3",
|
||||||
|
"Bankkonto-Nummer 3",
|
||||||
|
"Länderkennzeichen 3",
|
||||||
|
"IBAN 3",
|
||||||
|
"Leerfeld 3",
|
||||||
|
"SWIFT-Code 3",
|
||||||
|
"Abw. Kontoinhaber 3",
|
||||||
|
"Kennz. Haupt-Bankverb. 3",
|
||||||
|
"Bankverb. 3 gültig von",
|
||||||
|
"Bankverb. 3 gültig bis",
|
||||||
|
"Bankleitzahl 4",
|
||||||
|
"Bankbezeichnung 4",
|
||||||
|
"Bankkonto-Nummer 4",
|
||||||
|
"Länderkennzeichen 4",
|
||||||
|
"IBAN 4",
|
||||||
|
"Leerfeld 4",
|
||||||
|
"SWIFT-Code 4",
|
||||||
|
"Abw. Kontoinhaber 4",
|
||||||
|
"Kennz. Haupt-Bankverb. 4",
|
||||||
|
"Bankverb. 4 Gültig von",
|
||||||
|
"Bankverb. 4 Gültig bis",
|
||||||
|
"Bankleitzahl 5",
|
||||||
|
"Bankbezeichnung 5",
|
||||||
|
"Bankkonto-Nummer 5",
|
||||||
|
"Länderkennzeichen 5",
|
||||||
|
"IBAN 5",
|
||||||
|
"Leerfeld 5",
|
||||||
|
"SWIFT-Code 5",
|
||||||
|
"Abw. Kontoinhaber 5",
|
||||||
|
"Kennz. Haupt-Bankverb. 5",
|
||||||
|
"Bankverb. 5 gültig von",
|
||||||
|
"Bankverb. 5 gültig bis",
|
||||||
|
"Leerfeld 6",
|
||||||
|
"Briefanrede",
|
||||||
|
"Grußformel",
|
||||||
|
"Kundennummer",
|
||||||
|
"Steuernummer",
|
||||||
|
"Sprache",
|
||||||
|
"Ansprechpartner",
|
||||||
|
"Vertreter",
|
||||||
|
"Sachbearbeiter",
|
||||||
|
"Diverse-Konto",
|
||||||
|
"Ausgabeziel",
|
||||||
|
"Währungssteuerung",
|
||||||
|
"Kreditlimit (Debitor)",
|
||||||
|
"Zahlungsbedingung",
|
||||||
|
"Fälligkeit in Tagen (Debitor)",
|
||||||
|
"Skonto in Prozent (Debitor)",
|
||||||
|
"Kreditoren-Ziel 1 (Tage)",
|
||||||
|
"Kreditoren-Skonto 1 (%)",
|
||||||
|
"Kreditoren-Ziel 2 (Tage)",
|
||||||
|
"Kreditoren-Skonto 2 (%)",
|
||||||
|
"Kreditoren-Ziel 3 Brutto (Tage)",
|
||||||
|
"Kreditoren-Ziel 4 (Tage)",
|
||||||
|
"Kreditoren-Skonto 4 (%)",
|
||||||
|
"Kreditoren-Ziel 5 (Tage)",
|
||||||
|
"Kreditoren-Skonto 5 (%)",
|
||||||
|
"Mahnung",
|
||||||
|
"Kontoauszug",
|
||||||
|
"Mahntext 1",
|
||||||
|
"Mahntext 2",
|
||||||
|
"Mahntext 3",
|
||||||
|
"Kontoauszugstext",
|
||||||
|
"Mahnlimit Betrag",
|
||||||
|
"Mahnlimit %",
|
||||||
|
"Zinsberechnung",
|
||||||
|
"Mahnzinssatz 1",
|
||||||
|
"Mahnzinssatz 2",
|
||||||
|
"Mahnzinssatz 3",
|
||||||
|
"Lastschrift",
|
||||||
|
"Verfahren",
|
||||||
|
"Mandantenbank",
|
||||||
|
"Zahlungsträger",
|
||||||
|
"Indiv. Feld 1",
|
||||||
|
"Indiv. Feld 2",
|
||||||
|
"Indiv. Feld 3",
|
||||||
|
"Indiv. Feld 4",
|
||||||
|
"Indiv. Feld 5",
|
||||||
|
"Indiv. Feld 6",
|
||||||
|
"Indiv. Feld 7",
|
||||||
|
"Indiv. Feld 8",
|
||||||
|
"Indiv. Feld 9",
|
||||||
|
"Indiv. Feld 10",
|
||||||
|
"Indiv. Feld 11",
|
||||||
|
"Indiv. Feld 12",
|
||||||
|
"Indiv. Feld 13",
|
||||||
|
"Indiv. Feld 14",
|
||||||
|
"Indiv. Feld 15",
|
||||||
|
"Abweichende Anrede (Rechnungsadresse)",
|
||||||
|
"Adressart (Rechnungsadresse)",
|
||||||
|
"Straße (Rechnungsadresse)",
|
||||||
|
"Postfach (Rechnungsadresse)",
|
||||||
|
"Postleitzahl (Rechnungsadresse)",
|
||||||
|
"Ort (Rechnungsadresse)",
|
||||||
|
"Land (Rechnungsadresse)",
|
||||||
|
"Versandzusatz (Rechnungsadresse)",
|
||||||
|
"Adresszusatz (Rechnungsadresse)",
|
||||||
|
"Abw. Zustellbezeichnung 1 (Rechnungsadresse)",
|
||||||
|
"Abw. Zustellbezeichnung 2 (Rechnungsadresse)",
|
||||||
|
"Adresse Gültig von (Rechnungsadresse)",
|
||||||
|
"Adresse Gültig bis (Rechnungsadresse)",
|
||||||
|
"Bankleitzahl 6",
|
||||||
|
"Bankbezeichnung 6",
|
||||||
|
"Bankkonto-Nummer 6",
|
||||||
|
"Länderkennzeichen 6",
|
||||||
|
"IBAN 6",
|
||||||
|
"Leerfeld 7",
|
||||||
|
"SWIFT-Code 6",
|
||||||
|
"Abw. Kontoinhaber 6",
|
||||||
|
"Kennz. Haupt-Bankverb. 6",
|
||||||
|
"Bankverb 6 gültig von",
|
||||||
|
"Bankverb 6 gültig bis",
|
||||||
|
"Bankleitzahl 7",
|
||||||
|
"Bankbezeichnung 7",
|
||||||
|
"Bankkonto-Nummer 7",
|
||||||
|
"Länderkennzeichen 7",
|
||||||
|
"IBAN 7",
|
||||||
|
"Leerfeld 8",
|
||||||
|
"SWIFT-Code 7",
|
||||||
|
"Abw. Kontoinhaber 7",
|
||||||
|
"Kennz. Haupt-Bankverb. 7",
|
||||||
|
"Bankverb 7 gültig von",
|
||||||
|
"Bankverb 7 gültig bis",
|
||||||
|
"Bankleitzahl 8",
|
||||||
|
"Bankbezeichnung 8",
|
||||||
|
"Bankkonto-Nummer 8",
|
||||||
|
"Länderkennzeichen 8",
|
||||||
|
"IBAN 8",
|
||||||
|
"Leerfeld 9",
|
||||||
|
"SWIFT-Code 8",
|
||||||
|
"Abw. Kontoinhaber 8",
|
||||||
|
"Kennz. Haupt-Bankverb. 8",
|
||||||
|
"Bankverb 8 gültig von",
|
||||||
|
"Bankverb 8 gültig bis",
|
||||||
|
"Bankleitzahl 9",
|
||||||
|
"Bankbezeichnung 9",
|
||||||
|
"Bankkonto-Nummer 9",
|
||||||
|
"Länderkennzeichen 9",
|
||||||
|
"IBAN 9",
|
||||||
|
"Leerfeld 10",
|
||||||
|
"SWIFT-Code 9",
|
||||||
|
"Abw. Kontoinhaber 9",
|
||||||
|
"Kennz. Haupt-Bankverb. 9",
|
||||||
|
"Bankverb 9 gültig von",
|
||||||
|
"Bankverb 9 gültig bis",
|
||||||
|
"Bankleitzahl 10",
|
||||||
|
"Bankbezeichnung 10",
|
||||||
|
"Bankkonto-Nummer 10",
|
||||||
|
"Länderkennzeichen 10",
|
||||||
|
"IBAN 10",
|
||||||
|
"Leerfeld 11",
|
||||||
|
"SWIFT-Code 10",
|
||||||
|
"Abw. Kontoinhaber 10",
|
||||||
|
"Kennz. Haupt-Bankverb. 10",
|
||||||
|
"Bankverb 10 gültig von",
|
||||||
|
"Bankverb 10 gültig bis",
|
||||||
|
"Nummer Fremdsystem",
|
||||||
|
"Insolvent",
|
||||||
|
"SEPA-Mandatsreferenz 1",
|
||||||
|
"SEPA-Mandatsreferenz 2",
|
||||||
|
"SEPA-Mandatsreferenz 3",
|
||||||
|
"SEPA-Mandatsreferenz 4",
|
||||||
|
"SEPA-Mandatsreferenz 5",
|
||||||
|
"SEPA-Mandatsreferenz 6",
|
||||||
|
"SEPA-Mandatsreferenz 7",
|
||||||
|
"SEPA-Mandatsreferenz 8",
|
||||||
|
"SEPA-Mandatsreferenz 9",
|
||||||
|
"SEPA-Mandatsreferenz 10",
|
||||||
|
"Verknüpftes OPOS-Konto",
|
||||||
|
"Mahnsperre bis",
|
||||||
|
"Lastschriftsperre bis",
|
||||||
|
"Zahlungssperre bis",
|
||||||
|
"Gebührenberechnung",
|
||||||
|
"Mahngebühr 1",
|
||||||
|
"Mahngebühr 2",
|
||||||
|
"Mahngebühr 3",
|
||||||
|
"Pauschalberechnung",
|
||||||
|
"Verzugspauschale 1",
|
||||||
|
"Verzugspauschale 2",
|
||||||
|
"Verzugspauschale 3",
|
||||||
|
"Alternativer Suchname",
|
||||||
|
"Status",
|
||||||
|
"Anschrift manuell geändert (Korrespondenzadresse)",
|
||||||
|
"Anschrift individuell (Korrespondenzadresse)",
|
||||||
|
"Anschrift manuell geändert (Rechnungsadresse)",
|
||||||
|
"Anschrift individuell (Rechnungsadresse)",
|
||||||
|
"Fristberechnung bei Debitor",
|
||||||
|
"Mahnfrist 1",
|
||||||
|
"Mahnfrist 2",
|
||||||
|
"Mahnfrist 3",
|
||||||
|
"Letzte Frist"
|
||||||
|
]
|
||||||
|
|
||||||
|
ACCOUNT_NAME_COLUMNS = [
|
||||||
|
# Account number
|
||||||
|
"Konto",
|
||||||
|
# Account name
|
||||||
|
"Kontenbeschriftung",
|
||||||
|
# Language of the account name
|
||||||
|
# "de-DE" or "en-GB"
|
||||||
|
"Sprach-ID"
|
||||||
|
]
|
||||||
|
|
||||||
|
QUERY_REPORT_COLUMNS = [
|
||||||
|
{
|
||||||
|
"label": "Umsatz (ohne Soll/Haben-Kz)",
|
||||||
|
"fieldname": "Umsatz (ohne Soll/Haben-Kz)",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Soll/Haben-Kennzeichen",
|
||||||
|
"fieldname": "Soll/Haben-Kennzeichen",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Kontonummer",
|
||||||
|
"fieldname": "Kontonummer",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Gegenkonto (ohne BU-Schlüssel)",
|
||||||
|
"fieldname": "Gegenkonto (ohne BU-Schlüssel)",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Belegdatum",
|
||||||
|
"fieldname": "Belegdatum",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Buchungstext",
|
||||||
|
"fieldname": "Buchungstext",
|
||||||
|
"fieldtype": "Text",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Beleginfo - Art 1",
|
||||||
|
"fieldname": "Beleginfo - Art 1",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Beleginfo - Inhalt 1",
|
||||||
|
"fieldname": "Beleginfo - Inhalt 1",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Beleginfo - Art 2",
|
||||||
|
"fieldname": "Beleginfo - Art 2",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Beleginfo - Inhalt 2",
|
||||||
|
"fieldname": "Beleginfo - Inhalt 2",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
class DataCategory():
|
||||||
|
"""Field of the CSV Header."""
|
||||||
|
|
||||||
|
DEBTORS_CREDITORS = "16"
|
||||||
|
ACCOUNT_NAMES = "20"
|
||||||
|
TRANSACTIONS = "21"
|
||||||
|
POSTING_TEXT_CONSTANTS = "67"
|
||||||
|
|
||||||
|
class FormatName():
|
||||||
|
"""Field of the CSV Header, corresponds to DataCategory."""
|
||||||
|
|
||||||
|
DEBTORS_CREDITORS = "Debitoren/Kreditoren"
|
||||||
|
ACCOUNT_NAMES = "Kontenbeschriftungen"
|
||||||
|
TRANSACTIONS = "Buchungsstapel"
|
||||||
|
POSTING_TEXT_CONSTANTS = "Buchungstextkonstanten"
|
||||||
|
|
||||||
|
class Transactions():
|
||||||
|
DATA_CATEGORY = DataCategory.TRANSACTIONS
|
||||||
|
FORMAT_NAME = FormatName.TRANSACTIONS
|
||||||
|
COLUMNS = TRANSACTION_COLUMNS
|
||||||
|
|
||||||
|
class DebtorsCreditors():
|
||||||
|
DATA_CATEGORY = DataCategory.DEBTORS_CREDITORS
|
||||||
|
FORMAT_NAME = FormatName.DEBTORS_CREDITORS
|
||||||
|
COLUMNS = DEBTOR_CREDITOR_COLUMNS
|
||||||
|
|
||||||
|
class AccountNames():
|
||||||
|
DATA_CATEGORY = DataCategory.ACCOUNT_NAMES
|
||||||
|
FORMAT_NAME = FormatName.ACCOUNT_NAMES
|
||||||
|
COLUMNS = ACCOUNT_NAME_COLUMNS
|
||||||
244
erpnext/regional/report/datev/test_datev.py
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import zipfile
|
||||||
|
from six import BytesIO
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.utils import getdate, today, now_datetime, cstr
|
||||||
|
from frappe.test_runner import make_test_objects
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts
|
||||||
|
|
||||||
|
from erpnext.regional.report.datev.datev import validate
|
||||||
|
from erpnext.regional.report.datev.datev import get_transactions
|
||||||
|
from erpnext.regional.report.datev.datev import get_customers
|
||||||
|
from erpnext.regional.report.datev.datev import get_suppliers
|
||||||
|
from erpnext.regional.report.datev.datev import get_account_names
|
||||||
|
from erpnext.regional.report.datev.datev import get_datev_csv
|
||||||
|
from erpnext.regional.report.datev.datev import get_header
|
||||||
|
from erpnext.regional.report.datev.datev import download_datev_csv
|
||||||
|
|
||||||
|
from erpnext.regional.report.datev.datev_constants import DataCategory
|
||||||
|
from erpnext.regional.report.datev.datev_constants import Transactions
|
||||||
|
from erpnext.regional.report.datev.datev_constants import DebtorsCreditors
|
||||||
|
from erpnext.regional.report.datev.datev_constants import AccountNames
|
||||||
|
from erpnext.regional.report.datev.datev_constants import QUERY_REPORT_COLUMNS
|
||||||
|
|
||||||
|
def make_company(company_name, abbr):
|
||||||
|
if not frappe.db.exists("Company", company_name):
|
||||||
|
company = frappe.get_doc({
|
||||||
|
"doctype": "Company",
|
||||||
|
"company_name": company_name,
|
||||||
|
"abbr": abbr,
|
||||||
|
"default_currency": "EUR",
|
||||||
|
"country": "Germany",
|
||||||
|
"create_chart_of_accounts_based_on": "Standard Template",
|
||||||
|
"chart_of_accounts": "SKR04 mit Kontonummern"
|
||||||
|
})
|
||||||
|
company.insert()
|
||||||
|
else:
|
||||||
|
company = frappe.get_doc("Company", company_name)
|
||||||
|
|
||||||
|
# indempotent
|
||||||
|
company.create_default_warehouses()
|
||||||
|
|
||||||
|
if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": company.name}):
|
||||||
|
company.create_default_cost_center()
|
||||||
|
|
||||||
|
company.save()
|
||||||
|
return company
|
||||||
|
|
||||||
|
def setup_fiscal_year():
|
||||||
|
fiscal_year = None
|
||||||
|
year = cstr(now_datetime().year)
|
||||||
|
if not frappe.db.get_value("Fiscal Year", {"year": year}, "name"):
|
||||||
|
try:
|
||||||
|
fiscal_year = frappe.get_doc({
|
||||||
|
"doctype": "Fiscal Year",
|
||||||
|
"year": year,
|
||||||
|
"year_start_date": "{0}-01-01".format(year),
|
||||||
|
"year_end_date": "{0}-12-31".format(year)
|
||||||
|
})
|
||||||
|
fiscal_year.insert()
|
||||||
|
except frappe.NameError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if fiscal_year:
|
||||||
|
fiscal_year.set_as_default()
|
||||||
|
|
||||||
|
def make_customer_with_account(customer_name, company):
|
||||||
|
acc_name = frappe.db.get_value("Account", {
|
||||||
|
"account_name": customer_name,
|
||||||
|
"company": company.name
|
||||||
|
}, "name")
|
||||||
|
|
||||||
|
if not acc_name:
|
||||||
|
acc = frappe.get_doc({
|
||||||
|
"doctype": "Account",
|
||||||
|
"parent_account": "1 - Forderungen aus Lieferungen und Leistungen - _TG",
|
||||||
|
"account_name": customer_name,
|
||||||
|
"company": company.name,
|
||||||
|
"account_type": "Receivable",
|
||||||
|
"account_number": "10001"
|
||||||
|
})
|
||||||
|
acc.insert()
|
||||||
|
acc_name = acc.name
|
||||||
|
|
||||||
|
if not frappe.db.exists("Customer", customer_name):
|
||||||
|
customer = frappe.get_doc({
|
||||||
|
"doctype": "Customer",
|
||||||
|
"customer_name": customer_name,
|
||||||
|
"customer_type": "Company",
|
||||||
|
"accounts": [{
|
||||||
|
"company": company.name,
|
||||||
|
"account": acc_name
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
customer.insert()
|
||||||
|
else:
|
||||||
|
customer = frappe.get_doc("Customer", customer_name)
|
||||||
|
|
||||||
|
return customer
|
||||||
|
|
||||||
|
def make_item(item_code, company):
|
||||||
|
warehouse_name = frappe.db.get_value("Warehouse", {
|
||||||
|
"warehouse_name": "Stores",
|
||||||
|
"company": company.name
|
||||||
|
}, "name")
|
||||||
|
|
||||||
|
if not frappe.db.exists("Item", item_code):
|
||||||
|
item = frappe.get_doc({
|
||||||
|
"doctype": "Item",
|
||||||
|
"item_code": item_code,
|
||||||
|
"item_name": item_code,
|
||||||
|
"description": item_code,
|
||||||
|
"item_group": "All Item Groups",
|
||||||
|
"is_stock_item": 0,
|
||||||
|
"is_purchase_item": 0,
|
||||||
|
"is_customer_provided_item": 0,
|
||||||
|
"item_defaults": [{
|
||||||
|
"default_warehouse": warehouse_name,
|
||||||
|
"company": company.name
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
item.insert()
|
||||||
|
else:
|
||||||
|
item = frappe.get_doc("Item", item_code)
|
||||||
|
return item
|
||||||
|
|
||||||
|
def make_datev_settings(company):
|
||||||
|
if not frappe.db.exists("DATEV Settings", company.name):
|
||||||
|
frappe.get_doc({
|
||||||
|
"doctype": "DATEV Settings",
|
||||||
|
"client": company.name,
|
||||||
|
"client_number": "12345",
|
||||||
|
"consultant_number": "67890"
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
|
||||||
|
class TestDatev(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.company = make_company("_Test GmbH", "_TG")
|
||||||
|
self.customer = make_customer_with_account("_Test Kunde GmbH", self.company)
|
||||||
|
self.filters = {
|
||||||
|
"company": self.company.name,
|
||||||
|
"from_date": today(),
|
||||||
|
"to_date": today()
|
||||||
|
}
|
||||||
|
|
||||||
|
make_datev_settings(self.company)
|
||||||
|
item = make_item("_Test Item", self.company)
|
||||||
|
setup_fiscal_year()
|
||||||
|
|
||||||
|
warehouse = frappe.db.get_value("Item Default", {
|
||||||
|
"parent": item.name,
|
||||||
|
"company": self.company.name
|
||||||
|
}, "default_warehouse")
|
||||||
|
|
||||||
|
income_account = frappe.db.get_value("Account", {
|
||||||
|
"account_number": "4200",
|
||||||
|
"company": self.company.name
|
||||||
|
}, "name")
|
||||||
|
|
||||||
|
tax_account = frappe.db.get_value("Account", {
|
||||||
|
"account_number": "3806",
|
||||||
|
"company": self.company.name
|
||||||
|
}, "name")
|
||||||
|
|
||||||
|
si = create_sales_invoice(
|
||||||
|
company=self.company.name,
|
||||||
|
customer=self.customer.name,
|
||||||
|
currency=self.company.default_currency,
|
||||||
|
debit_to=self.customer.accounts[0].account,
|
||||||
|
income_account="4200 - Erlöse - _TG",
|
||||||
|
expense_account="6990 - Herstellungskosten - _TG",
|
||||||
|
cost_center=self.company.cost_center,
|
||||||
|
warehouse=warehouse,
|
||||||
|
item=item.name,
|
||||||
|
do_not_save=1
|
||||||
|
)
|
||||||
|
|
||||||
|
si.append("taxes", {
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": tax_account,
|
||||||
|
"description": "Umsatzsteuer 19 %",
|
||||||
|
"rate": 19
|
||||||
|
})
|
||||||
|
|
||||||
|
si.save()
|
||||||
|
si.submit()
|
||||||
|
|
||||||
|
def test_columns(self):
|
||||||
|
def is_subset(get_data, allowed_keys):
|
||||||
|
"""
|
||||||
|
Validate that the dict contains only allowed keys.
|
||||||
|
|
||||||
|
Params:
|
||||||
|
get_data -- Function that returns a list of dicts.
|
||||||
|
allowed_keys -- List of allowed keys
|
||||||
|
"""
|
||||||
|
data = get_data(self.filters)
|
||||||
|
if data == []:
|
||||||
|
# No data and, therefore, no columns is okay
|
||||||
|
return True
|
||||||
|
actual_set = set(data[0].keys())
|
||||||
|
# allowed set must be interpreted as unicode to match the actual set
|
||||||
|
allowed_set = set({frappe.as_unicode(key) for key in allowed_keys})
|
||||||
|
return actual_set.issubset(allowed_set)
|
||||||
|
|
||||||
|
self.assertTrue(is_subset(get_transactions, Transactions.COLUMNS))
|
||||||
|
self.assertTrue(is_subset(get_customers, DebtorsCreditors.COLUMNS))
|
||||||
|
self.assertTrue(is_subset(get_suppliers, DebtorsCreditors.COLUMNS))
|
||||||
|
self.assertTrue(is_subset(get_account_names, AccountNames.COLUMNS))
|
||||||
|
|
||||||
|
def test_header(self):
|
||||||
|
self.assertTrue(Transactions.DATA_CATEGORY in get_header(self.filters, Transactions))
|
||||||
|
self.assertTrue(AccountNames.DATA_CATEGORY in get_header(self.filters, AccountNames))
|
||||||
|
self.assertTrue(DebtorsCreditors.DATA_CATEGORY in get_header(self.filters, DebtorsCreditors))
|
||||||
|
|
||||||
|
def test_csv(self):
|
||||||
|
test_data = [{
|
||||||
|
"Umsatz (ohne Soll/Haben-Kz)": 100,
|
||||||
|
"Soll/Haben-Kennzeichen": "H",
|
||||||
|
"Kontonummer": "4200",
|
||||||
|
"Gegenkonto (ohne BU-Schlüssel)": "10000",
|
||||||
|
"Belegdatum": today(),
|
||||||
|
"Buchungstext": "No remark",
|
||||||
|
"Beleginfo - Art 1": "Sales Invoice",
|
||||||
|
"Beleginfo - Inhalt 1": "SINV-0001"
|
||||||
|
}]
|
||||||
|
get_datev_csv(data=test_data, filters=self.filters, csv_class=Transactions)
|
||||||
|
|
||||||
|
def test_download(self):
|
||||||
|
"""Assert that the returned file is a ZIP file."""
|
||||||
|
download_datev_csv(self.filters)
|
||||||
|
|
||||||
|
# zipfile.is_zipfile() expects a file-like object
|
||||||
|
zip_buffer = BytesIO()
|
||||||
|
zip_buffer.write(frappe.response['filecontent'])
|
||||||
|
|
||||||
|
self.assertTrue(zipfile.is_zipfile(zip_buffer))
|
||||||
@ -52,7 +52,7 @@ frappe.query_reports["GSTR-1"] = {
|
|||||||
],
|
],
|
||||||
onload: function (report) {
|
onload: function (report) {
|
||||||
|
|
||||||
report.page.add_inner_button(__("Download as Json"), function () {
|
report.page.add_inner_button(__("Download as JSON"), function () {
|
||||||
var filters = report.get_values();
|
var filters = report.get_values();
|
||||||
|
|
||||||
const args = {
|
const args = {
|
||||||
|
|||||||
@ -204,6 +204,40 @@ class Customer(TransactionBase):
|
|||||||
else:
|
else:
|
||||||
frappe.msgprint(_("Multiple Loyalty Program found for the Customer. Please select manually."))
|
frappe.msgprint(_("Multiple Loyalty Program found for the Customer. Please select manually."))
|
||||||
|
|
||||||
|
def create_onboarding_docs(self, args):
|
||||||
|
defaults = frappe.defaults.get_defaults()
|
||||||
|
for i in range(1, args.get('max_count')):
|
||||||
|
customer = args.get('customer_name_' + str(i))
|
||||||
|
if customer:
|
||||||
|
try:
|
||||||
|
doc = frappe.get_doc({
|
||||||
|
'doctype': self.doctype,
|
||||||
|
'customer_name': customer,
|
||||||
|
'customer_type': 'Company',
|
||||||
|
'customer_group': _('Commercial'),
|
||||||
|
'territory': defaults.get('country'),
|
||||||
|
'company': defaults.get('company')
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
if args.get('customer_email_' + str(i)):
|
||||||
|
create_contact(customer, self.doctype,
|
||||||
|
doc.name, args.get("customer_email_" + str(i)))
|
||||||
|
except frappe.NameError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_contact(contact, party_type, party, email):
|
||||||
|
"""Create contact based on given contact name"""
|
||||||
|
contact = contact.split(' ')
|
||||||
|
|
||||||
|
contact = frappe.get_doc({
|
||||||
|
'doctype': 'Contact',
|
||||||
|
'first_name': contact[0],
|
||||||
|
'last_name': len(contact) > 1 and contact[1] or ""
|
||||||
|
})
|
||||||
|
contact.append('email_ids', dict(email_id=email, is_primary=1))
|
||||||
|
contact.append('links', dict(link_doctype=party_type, link_name=party))
|
||||||
|
contact.insert()
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_loyalty_programs(doc):
|
def get_loyalty_programs(doc):
|
||||||
''' returns applicable loyalty programs for a customer '''
|
''' returns applicable loyalty programs for a customer '''
|
||||||
|
|||||||
@ -202,7 +202,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// payment request
|
// payment request
|
||||||
if(flt(doc.per_billed)==0) {
|
if(flt(doc.per_billed)<100) {
|
||||||
this.frm.add_custom_button(__('Payment Request'), () => this.make_payment_request(), __('Create'));
|
this.frm.add_custom_button(__('Payment Request'), () => this.make_payment_request(), __('Create'));
|
||||||
this.frm.add_custom_button(__('Payment'), () => this.make_payment_entry(), __('Create'));
|
this.frm.add_custom_button(__('Payment'), () => this.make_payment_entry(), __('Create'));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -834,6 +834,10 @@ def make_purchase_order(source_name, for_supplier=None, selected_items=[], targe
|
|||||||
for item in sales_order.items:
|
for item in sales_order.items:
|
||||||
if item.supplier and item.supplier not in suppliers:
|
if item.supplier and item.supplier not in suppliers:
|
||||||
suppliers.append(item.supplier)
|
suppliers.append(item.supplier)
|
||||||
|
|
||||||
|
if not suppliers:
|
||||||
|
frappe.throw(_("Please set a Supplier against the Items to be considered in the Purchase Order."))
|
||||||
|
|
||||||
for supplier in suppliers:
|
for supplier in suppliers:
|
||||||
po =frappe.get_list("Purchase Order", filters={"sales_order":source_name, "supplier":supplier, "docstatus": ("<", "2")})
|
po =frappe.get_list("Purchase Order", filters={"sales_order":source_name, "supplier":supplier, "docstatus": ("<", "2")})
|
||||||
if len(po) == 0:
|
if len(po) == 0:
|
||||||
|
|||||||
@ -12,6 +12,7 @@ from erpnext.selling.doctype.sales_order.sales_order import make_work_orders
|
|||||||
from erpnext.controllers.accounts_controller import update_child_qty_rate
|
from erpnext.controllers.accounts_controller import update_child_qty_rate
|
||||||
import json
|
import json
|
||||||
from erpnext.selling.doctype.sales_order.sales_order import make_raw_material_request
|
from erpnext.selling.doctype.sales_order.sales_order import make_raw_material_request
|
||||||
|
from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order
|
||||||
|
|
||||||
class TestSalesOrder(unittest.TestCase):
|
class TestSalesOrder(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
@ -819,6 +820,25 @@ class TestSalesOrder(unittest.TestCase):
|
|||||||
mr_doc = frappe.get_doc('Material Request',mr.get('name'))
|
mr_doc = frappe.get_doc('Material Request',mr.get('name'))
|
||||||
self.assertEqual(mr_doc.items[0].sales_order, so.name)
|
self.assertEqual(mr_doc.items[0].sales_order, so.name)
|
||||||
|
|
||||||
|
def test_so_optional_blanket_order(self):
|
||||||
|
"""
|
||||||
|
Expected result: Blanket order Ordered Quantity should only be affected on Sales Order with against_blanket_order = 1.
|
||||||
|
Second Sales Order should not add on to Blanket Orders Ordered Quantity.
|
||||||
|
"""
|
||||||
|
|
||||||
|
bo = make_blanket_order(blanket_order_type = "Selling", quantity = 10, rate = 10)
|
||||||
|
|
||||||
|
so = make_sales_order(item_code= "_Test Item", qty = 5, against_blanket_order = 1)
|
||||||
|
so_doc = frappe.get_doc('Sales Order', so.get('name'))
|
||||||
|
# To test if the SO has a Blanket Order
|
||||||
|
self.assertTrue(so_doc.items[0].blanket_order)
|
||||||
|
|
||||||
|
so = make_sales_order(item_code= "_Test Item", qty = 5, against_blanket_order = 0)
|
||||||
|
so_doc = frappe.get_doc('Sales Order', so.get('name'))
|
||||||
|
# To test if the SO does NOT have a Blanket Order
|
||||||
|
self.assertEqual(so_doc.items[0].blanket_order, None)
|
||||||
|
|
||||||
|
|
||||||
def make_sales_order(**args):
|
def make_sales_order(**args):
|
||||||
so = frappe.new_doc("Sales Order")
|
so = frappe.new_doc("Sales Order")
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
@ -845,7 +865,8 @@ def make_sales_order(**args):
|
|||||||
"warehouse": args.warehouse,
|
"warehouse": args.warehouse,
|
||||||
"qty": args.qty or 10,
|
"qty": args.qty or 10,
|
||||||
"uom": args.uom or None,
|
"uom": args.uom or None,
|
||||||
"rate": args.rate or 100
|
"rate": args.rate or 100,
|
||||||
|
"against_blanket_order": args.against_blanket_order
|
||||||
})
|
})
|
||||||
|
|
||||||
so.delivery_date = add_days(so.transaction_date, 10)
|
so.delivery_date = add_days(so.transaction_date, 10)
|
||||||
|
|||||||
@ -68,6 +68,7 @@
|
|||||||
"target_warehouse",
|
"target_warehouse",
|
||||||
"prevdoc_docname",
|
"prevdoc_docname",
|
||||||
"col_break4",
|
"col_break4",
|
||||||
|
"against_blanket_order",
|
||||||
"blanket_order",
|
"blanket_order",
|
||||||
"blanket_order_rate",
|
"blanket_order_rate",
|
||||||
"planning_section",
|
"planning_section",
|
||||||
@ -574,6 +575,7 @@
|
|||||||
"report_hide": 1
|
"report_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:doc.against_blanket_order",
|
||||||
"fieldname": "blanket_order",
|
"fieldname": "blanket_order",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Blanket Order",
|
"label": "Blanket Order",
|
||||||
@ -581,6 +583,7 @@
|
|||||||
"options": "Blanket Order"
|
"options": "Blanket Order"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:doc.against_blanket_order",
|
||||||
"fieldname": "blanket_order_rate",
|
"fieldname": "blanket_order_rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Blanket Order Rate",
|
"label": "Blanket Order Rate",
|
||||||
@ -741,11 +744,17 @@
|
|||||||
"fieldname": "image_section",
|
"fieldname": "image_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Image"
|
"label": "Image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "against_blanket_order",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Against Blanket Order"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2019-10-10 08:46:26.244823",
|
"modified": "2019-11-19 14:19:29.491945",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Sales Order Item",
|
"name": "Sales Order Item",
|
||||||
|
|||||||
@ -1,611 +1,159 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2013-06-25 10:25:16",
|
"creation": "2013-06-25 10:25:16",
|
||||||
"custom": 0,
|
|
||||||
"description": "Settings for Selling Module",
|
"description": "Settings for Selling Module",
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Other",
|
"document_type": "Other",
|
||||||
"editable_grid": 0,
|
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"cust_master_name",
|
||||||
|
"campaign_naming_by",
|
||||||
|
"customer_group",
|
||||||
|
"territory",
|
||||||
|
"selling_price_list",
|
||||||
|
"close_opportunity_after_days",
|
||||||
|
"default_valid_till",
|
||||||
|
"column_break_5",
|
||||||
|
"so_required",
|
||||||
|
"dn_required",
|
||||||
|
"sales_update_frequency",
|
||||||
|
"maintain_same_sales_rate",
|
||||||
|
"editable_price_list_rate",
|
||||||
|
"allow_multiple_items",
|
||||||
|
"allow_against_multiple_purchase_orders",
|
||||||
|
"validate_selling_price",
|
||||||
|
"hide_tax_id"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "Customer Name",
|
"default": "Customer Name",
|
||||||
"fieldname": "cust_master_name",
|
"fieldname": "cust_master_name",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Customer Naming By",
|
"label": "Customer Naming By",
|
||||||
"length": 0,
|
"options": "Customer Name\nNaming Series"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Customer Name\nNaming Series",
|
|
||||||
"permlevel": 0,
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "campaign_naming_by",
|
"fieldname": "campaign_naming_by",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Campaign Naming By",
|
"label": "Campaign Naming By",
|
||||||
"length": 0,
|
"options": "Campaign Name\nNaming Series"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Campaign Name\nNaming Series",
|
|
||||||
"permlevel": 0,
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"description": "",
|
|
||||||
"fieldname": "customer_group",
|
"fieldname": "customer_group",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Default Customer Group",
|
"label": "Default Customer Group",
|
||||||
"length": 0,
|
"options": "Customer Group"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Customer Group",
|
|
||||||
"permlevel": 0,
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"description": "",
|
|
||||||
"fieldname": "territory",
|
"fieldname": "territory",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Default Territory",
|
"label": "Default Territory",
|
||||||
"length": 0,
|
"options": "Territory"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Territory",
|
|
||||||
"permlevel": 0,
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "selling_price_list",
|
"fieldname": "selling_price_list",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Default Price List",
|
"label": "Default Price List",
|
||||||
"length": 0,
|
"options": "Price List"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Price List",
|
|
||||||
"permlevel": 0,
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "15",
|
"default": "15",
|
||||||
"description": "Auto close Opportunity after 15 days",
|
"description": "Auto close Opportunity after 15 days",
|
||||||
"fieldname": "close_opportunity_after_days",
|
"fieldname": "close_opportunity_after_days",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"hidden": 0,
|
"label": "Close Opportunity After Days"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Close Opportunity After Days",
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "default_valid_till",
|
"fieldname": "default_valid_till",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
"label": "Default Quotation Validity Days"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Default Quotation Validity Days",
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_5",
|
"fieldname": "column_break_5",
|
||||||
"fieldtype": "Column Break",
|
"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,
|
|
||||||
"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,
|
"description": "Only for Stock Items",
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "so_required",
|
"fieldname": "so_required",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"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": "Sales Order Required",
|
"label": "Sales Order Required",
|
||||||
"length": 0,
|
"options": "No\nYes"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "No\nYes",
|
|
||||||
"permlevel": 0,
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "dn_required",
|
"fieldname": "dn_required",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"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": "Delivery Note Required",
|
"label": "Delivery Note Required",
|
||||||
"length": 0,
|
"options": "No\nYes"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "No\nYes",
|
|
||||||
"permlevel": 0,
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "Each Transaction",
|
"default": "Each Transaction",
|
||||||
"description": "How often should project and company be updated based on Sales Transactions.",
|
"description": "How often should project and company be updated based on Sales Transactions.",
|
||||||
"fieldname": "sales_update_frequency",
|
"fieldname": "sales_update_frequency",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"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": "Sales Update Frequency",
|
"label": "Sales Update Frequency",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Each Transaction\nDaily\nMonthly",
|
"options": "Each Transaction\nDaily\nMonthly",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"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,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "maintain_same_sales_rate",
|
"fieldname": "maintain_same_sales_rate",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"label": "Maintain Same Rate Throughout Sales Cycle"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Maintain Same Rate Throughout Sales Cycle",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"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,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "editable_price_list_rate",
|
"fieldname": "editable_price_list_rate",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"label": "Allow user to edit Price List Rate in transactions"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Allow user to edit Price List Rate in transactions",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"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,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "allow_multiple_items",
|
"fieldname": "allow_multiple_items",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"label": "Allow Item to be added multiple times in a transaction"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Allow Item to be added multiple times in a transaction",
|
|
||||||
"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,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "allow_against_multiple_purchase_orders",
|
"fieldname": "allow_against_multiple_purchase_orders",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"label": "Allow multiple Sales Orders against a Customer's Purchase Order"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Allow multiple Sales Orders against a Customer's Purchase Order",
|
|
||||||
"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,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "validate_selling_price",
|
"fieldname": "validate_selling_price",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"label": "Validate Selling Price for Item against Purchase Rate or Valuation Rate"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Validate Selling Price for Item against Purchase Rate or Valuation Rate",
|
|
||||||
"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,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "hide_tax_id",
|
"fieldname": "hide_tax_id",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"label": "Hide Customer's Tax Id from Sales Transactions"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Hide Customer's Tax Id from Sales Transactions",
|
|
||||||
"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
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"icon": "fa fa-cog",
|
"icon": "fa fa-cog",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"istable": 0,
|
"modified": "2019-11-25 18:35:51.472653",
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2018-06-25 12:56:16.332039",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Selling Settings",
|
"name": "Selling Settings",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 0,
|
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 0,
|
|
||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC"
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestSellingSettings(unittest.TestCase):
|
||||||
|
pass
|
||||||
@ -18,17 +18,18 @@ def execute(filters=None):
|
|||||||
|
|
||||||
for wh, item_qty in warehouse.items():
|
for wh, item_qty in warehouse.items():
|
||||||
total += 1
|
total += 1
|
||||||
row = [sbom, item_map.get(sbom).item_name, item_map.get(sbom).description,
|
if item_map.get(sbom):
|
||||||
item_map.get(sbom).stock_uom, wh]
|
row = [sbom, item_map.get(sbom).item_name, item_map.get(sbom).description,
|
||||||
available_qty = item_qty
|
item_map.get(sbom).stock_uom, wh]
|
||||||
total_qty += flt(available_qty)
|
available_qty = item_qty
|
||||||
row += [available_qty]
|
total_qty += flt(available_qty)
|
||||||
|
row += [available_qty]
|
||||||
|
|
||||||
if available_qty:
|
if available_qty:
|
||||||
data.append(row)
|
|
||||||
if (total == len(warehouse)):
|
|
||||||
row = ["", "", "Total", "", "", total_qty]
|
|
||||||
data.append(row)
|
data.append(row)
|
||||||
|
if (total == len(warehouse)):
|
||||||
|
row = ["", "", "Total", "", "", total_qty]
|
||||||
|
data.append(row)
|
||||||
return columns, data
|
return columns, data
|
||||||
|
|
||||||
def get_columns():
|
def get_columns():
|
||||||
|
|||||||
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"add_more_button": 1,
|
||||||
|
"app": "ERPNext",
|
||||||
|
"creation": "2019-11-15 14:44:10.065014",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Setup Wizard Slide",
|
||||||
|
"domains": [],
|
||||||
|
"help_links": [
|
||||||
|
{
|
||||||
|
"label": "Customers",
|
||||||
|
"video_id": "zsrrVDk6VBs"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"idx": 0,
|
||||||
|
"image_src": "/assets/erpnext/images/illustrations/customer.png",
|
||||||
|
"max_count": 3,
|
||||||
|
"modified": "2019-11-26 18:26:15.888794",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"name": "Add A Few Customers",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"ref_doctype": "Customer",
|
||||||
|
"slide_desc": "",
|
||||||
|
"slide_fields": [
|
||||||
|
{
|
||||||
|
"align": "",
|
||||||
|
"fieldname": "customer_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Customer Name",
|
||||||
|
"placeholder": "",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"align": "",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"reqd": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"align": "",
|
||||||
|
"fieldname": "customer_email",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Email ID",
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"slide_order": 40,
|
||||||
|
"slide_title": "Add A Few Customers",
|
||||||
|
"slide_type": "Create",
|
||||||
|
"submit_method": ""
|
||||||
|
}
|
||||||
@ -1,8 +0,0 @@
|
|||||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
// For license information, please see license.txt
|
|
||||||
|
|
||||||
frappe.ui.form.on('Setup Progress', {
|
|
||||||
refresh: function() {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,123 +0,0 @@
|
|||||||
{
|
|
||||||
"allow_copy": 0,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2017-08-27 21:01:42.032109",
|
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "actions_sb",
|
|
||||||
"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": "Actions",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "actions",
|
|
||||||
"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": "Actions",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Setup Progress Action",
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"has_web_view": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 0,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 1,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 1,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2017-09-21 11:52:56.106659",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Setup",
|
|
||||||
"name": "Setup Progress",
|
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"amend": 0,
|
|
||||||
"apply_user_permissions": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 0,
|
|
||||||
"delete": 0,
|
|
||||||
"email": 1,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 0,
|
|
||||||
"role": "All",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"quick_entry": 1,
|
|
||||||
"read_only": 1,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_changes": 1,
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
import frappe, json
|
|
||||||
from frappe.model.document import Document
|
|
||||||
|
|
||||||
class SetupProgress(Document):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_setup_progress():
|
|
||||||
if not getattr(frappe.local, "setup_progress", None):
|
|
||||||
frappe.local.setup_progress = frappe.get_doc("Setup Progress", "Setup Progress")
|
|
||||||
|
|
||||||
return frappe.local.setup_progress
|
|
||||||
|
|
||||||
def get_action_completed_state(action_name):
|
|
||||||
for d in get_setup_progress().actions:
|
|
||||||
if d.action_name == action_name:
|
|
||||||
return d.is_completed
|
|
||||||
|
|
||||||
def update_action_completed_state(action_name):
|
|
||||||
action_table_doc = [d for d in get_setup_progress().actions
|
|
||||||
if d.action_name == action_name][0]
|
|
||||||
update_action(action_table_doc)
|
|
||||||
|
|
||||||
def update_action(doc):
|
|
||||||
doctype = doc.action_doctype
|
|
||||||
docname = doc.action_document
|
|
||||||
field = doc.action_field
|
|
||||||
|
|
||||||
if not doc.is_completed:
|
|
||||||
if doc.min_doc_count:
|
|
||||||
if frappe.db.count(doctype) >= doc.min_doc_count:
|
|
||||||
doc.is_completed = 1
|
|
||||||
doc.save()
|
|
||||||
if docname and field:
|
|
||||||
d = frappe.get_doc(doctype, docname)
|
|
||||||
if d.get(field):
|
|
||||||
doc.is_completed = 1
|
|
||||||
doc.save()
|
|
||||||
|
|
||||||
def update_domain_actions(domain):
|
|
||||||
for d in get_setup_progress().actions:
|
|
||||||
domains = json.loads(d.domains)
|
|
||||||
if domains == [] or domain in domains:
|
|
||||||
update_action(d)
|
|
||||||
|
|
||||||
def get_domain_actions_state(domain):
|
|
||||||
state = {}
|
|
||||||
for d in get_setup_progress().actions:
|
|
||||||
domains = json.loads(d.domains)
|
|
||||||
if domains == [] or domain in domains:
|
|
||||||
state[d.action_name] = d.is_completed
|
|
||||||
return state
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def set_action_completed_state(action_name):
|
|
||||||
action_table_doc = [d for d in get_setup_progress().actions
|
|
||||||
if d.action_name == action_name][0]
|
|
||||||
action_table_doc.is_completed = 1
|
|
||||||
action_table_doc.save()
|
|
||||||
@ -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: Setup Progress", function (assert) {
|
|
||||||
let done = assert.async();
|
|
||||||
|
|
||||||
// number of asserts
|
|
||||||
assert.expect(1);
|
|
||||||
|
|
||||||
frappe.run_serially([
|
|
||||||
// insert a new Setup Progress
|
|
||||||
() => frappe.tests.make('Setup Progress', [
|
|
||||||
// values to be set
|
|
||||||
{key: 'value'}
|
|
||||||
]),
|
|
||||||
() => {
|
|
||||||
assert.equal(cur_frm.doc.key, 'value');
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
|
|
||||||
});
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
# See license.txt
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
class TestSetupProgress(unittest.TestCase):
|
|
||||||
pass
|
|
||||||
@ -1,253 +0,0 @@
|
|||||||
{
|
|
||||||
"allow_copy": 0,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2017-08-27 21:00:40.715360",
|
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "action_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": "Action 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": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "action_doctype",
|
|
||||||
"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": "Action Doctype",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "DocType",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "action_document",
|
|
||||||
"fieldtype": "Dynamic Link",
|
|
||||||
"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": "Action Document",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "action_doctype",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "action_field",
|
|
||||||
"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": "Action Field",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "min_doc_count",
|
|
||||||
"fieldtype": "Int",
|
|
||||||
"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": "Min Doc Count",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "domains",
|
|
||||||
"fieldtype": "Code",
|
|
||||||
"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": "Domains",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "is_completed",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"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": "Is Completed",
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"has_web_view": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 0,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 1,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 1,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2017-09-01 14:34:59.685730",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Setup",
|
|
||||||
"name": "Setup Progress Action",
|
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"quick_entry": 1,
|
|
||||||
"read_only": 1,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_changes": 1,
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
from frappe.model.document import Document
|
|
||||||
|
|
||||||
class SetupProgressAction(Document):
|
|
||||||
pass
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
# 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, time
|
|
||||||
from frappe.utils.selenium_testdriver import TestDriver
|
|
||||||
|
|
||||||
def run_setup_wizard_test():
|
|
||||||
driver = TestDriver()
|
|
||||||
frappe.db.set_default('in_selenium', '1')
|
|
||||||
frappe.db.commit()
|
|
||||||
|
|
||||||
driver.login('#page-setup-wizard')
|
|
||||||
print('Running Setup Wizard Test...')
|
|
||||||
|
|
||||||
# Language slide
|
|
||||||
driver.wait_for_ajax(True)
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
driver.set_select("language", "English (United States)")
|
|
||||||
driver.wait_for_ajax(True)
|
|
||||||
time.sleep(1)
|
|
||||||
driver.click(".next-btn")
|
|
||||||
|
|
||||||
# Region slide
|
|
||||||
driver.wait_for_ajax(True)
|
|
||||||
driver.set_select("country", "India")
|
|
||||||
driver.wait_for_ajax(True)
|
|
||||||
time.sleep(1)
|
|
||||||
driver.click(".next-btn")
|
|
||||||
|
|
||||||
# Profile slide
|
|
||||||
driver.set_field("full_name", "Great Tester")
|
|
||||||
driver.set_field("email", "great@example.com")
|
|
||||||
driver.set_field("password", "test")
|
|
||||||
driver.wait_for_ajax(True)
|
|
||||||
time.sleep(1)
|
|
||||||
driver.click(".next-btn")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# domain slide
|
|
||||||
driver.set_multicheck("domains", ["Manufacturing"])
|
|
||||||
time.sleep(1)
|
|
||||||
driver.click(".next-btn")
|
|
||||||
|
|
||||||
# Org slide
|
|
||||||
driver.set_field("company_name", "For Testing")
|
|
||||||
time.sleep(1)
|
|
||||||
driver.print_console()
|
|
||||||
driver.click(".next-btn")
|
|
||||||
|
|
||||||
driver.set_field("company_tagline", "Just for GST")
|
|
||||||
driver.set_field("bank_account", "HDFC")
|
|
||||||
time.sleep(3)
|
|
||||||
driver.click(".complete-btn")
|
|
||||||
|
|
||||||
# Wait for desktop
|
|
||||||
driver.wait_for('#page-desktop', timeout=600)
|
|
||||||
|
|
||||||
driver.print_console()
|
|
||||||
time.sleep(3)
|
|
||||||
|
|
||||||
frappe.db.set_default('in_selenium', None)
|
|
||||||
frappe.db.set_value("Company", "For Testing", "write_off_account", "Write Off - FT")
|
|
||||||
frappe.db.set_value("Company", "For Testing", "exchange_gain_loss_account", "Exchange Gain/Loss - FT")
|
|
||||||
frappe.db.commit()
|
|
||||||
|
|
||||||
driver.close()
|
|
||||||
|
|
||||||
return True
|
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"add_more_button": 0,
|
||||||
|
"app": "ERPNext",
|
||||||
|
"creation": "2019-11-26 17:01:26.671859",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Setup Wizard Slide",
|
||||||
|
"domains": [],
|
||||||
|
"help_links": [],
|
||||||
|
"idx": 0,
|
||||||
|
"image_src": "/assets/erpnext/images/illustrations/onboard.png",
|
||||||
|
"max_count": 0,
|
||||||
|
"modified": "2019-11-26 17:17:29.813299",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"name": "Welcome to ERPNext!",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"slide_desc": "Setting up an ERP can be overwhelming. But don't worry, we have got your back!<br>\nLet's setup your company.\nThis wizard will help you onboard to ERPNext in a short time!",
|
||||||
|
"slide_fields": [],
|
||||||
|
"slide_module": "Setup",
|
||||||
|
"slide_order": 10,
|
||||||
|
"slide_title": "Welcome to ERPNext!",
|
||||||
|
"slide_type": "Information"
|
||||||
|
}
|
||||||