Merge branch 'develop' into gross-profit-product-bundle
This commit is contained in:
commit
3e5bbc9aa6
@ -13,3 +13,6 @@
|
||||
|
||||
# This commit just changes spaces to tabs for indentation in some files
|
||||
5f473611bd6ed57703716244a054d3fb5ba9cd23
|
||||
|
||||
# Whitespace fix throughout codebase
|
||||
4551d7d6029b6f587f6c99d4f8df5519241c6a86
|
||||
|
@ -450,5 +450,3 @@ def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr):
|
||||
return debit_account
|
||||
else:
|
||||
return credit_account
|
||||
|
||||
|
||||
|
@ -113,5 +113,3 @@ def disable_dimension():
|
||||
dimension2 = frappe.get_doc("Accounting Dimension", "Location")
|
||||
dimension2.disabled = 1
|
||||
dimension2.save()
|
||||
|
||||
|
||||
|
@ -105,4 +105,3 @@ def unclear_reference_payment(doctype, docname):
|
||||
frappe.db.set_value(doc.payment_document, doc.payment_entry, "clearance_date", None)
|
||||
|
||||
return doc.payment_entry
|
||||
|
||||
|
@ -18,5 +18,3 @@ class CashFlowMapping(Document):
|
||||
frappe._('You can only select a maximum of one option from the list of check boxes.'),
|
||||
title='Error'
|
||||
)
|
||||
|
||||
|
||||
|
@ -62,6 +62,3 @@ def create_cost_center(**args):
|
||||
cc.is_group = args.is_group or 0
|
||||
cc.parent_cost_center = args.parent_cost_center or "_Test Company - _TC"
|
||||
cc.insert()
|
||||
|
||||
|
||||
|
||||
|
@ -124,6 +124,3 @@ class TestCouponCode(unittest.TestCase):
|
||||
|
||||
so.submit()
|
||||
self.assertEqual(frappe.db.get_value("Coupon Code", "SAVE30", "used"), 1)
|
||||
|
||||
|
||||
|
||||
|
@ -9,19 +9,8 @@ import frappe
|
||||
import unittest
|
||||
|
||||
class TestFinanceBook(unittest.TestCase):
|
||||
def create_finance_book(self):
|
||||
if not frappe.db.exists("Finance Book", "_Test Finance Book"):
|
||||
finance_book = frappe.get_doc({
|
||||
"doctype": "Finance Book",
|
||||
"finance_book_name": "_Test Finance Book"
|
||||
}).insert()
|
||||
else:
|
||||
finance_book = frappe.get_doc("Finance Book", "_Test Finance Book")
|
||||
|
||||
return finance_book
|
||||
|
||||
def test_finance_book(self):
|
||||
finance_book = self.create_finance_book()
|
||||
finance_book = create_finance_book()
|
||||
|
||||
# create jv entry
|
||||
jv = make_journal_entry("_Test Bank - _TC",
|
||||
@ -41,3 +30,14 @@ class TestFinanceBook(unittest.TestCase):
|
||||
|
||||
for gl_entry in gl_entries:
|
||||
self.assertEqual(gl_entry.finance_book, finance_book.name)
|
||||
|
||||
def create_finance_book():
|
||||
if not frappe.db.exists("Finance Book", "_Test Finance Book"):
|
||||
finance_book = frappe.get_doc({
|
||||
"doctype": "Finance Book",
|
||||
"finance_book_name": "_Test Finance Book"
|
||||
}).insert()
|
||||
else:
|
||||
finance_book = frappe.get_doc("Finance Book", "_Test Finance Book")
|
||||
|
||||
return finance_book
|
@ -39,4 +39,3 @@ class ModeofPayment(Document):
|
||||
message = "POS Profile " + frappe.bold(", ".join(pos_profiles)) + " contains \
|
||||
Mode of Payment " + frappe.bold(str(self.name)) + ". Please remove them to disable this mode."
|
||||
frappe.throw(_(message), title="Not Allowed")
|
||||
|
||||
|
@ -241,4 +241,3 @@ def get_temporary_opening_account(company=None):
|
||||
frappe.throw(_("Please add a Temporary Opening account in Chart of Accounts"))
|
||||
|
||||
return accounts[0].name
|
||||
|
||||
|
@ -533,8 +533,8 @@ frappe.ui.form.on('Payment Entry', {
|
||||
source_exchange_rate: function(frm) {
|
||||
if (frm.doc.paid_amount) {
|
||||
frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate));
|
||||
if(!frm.set_paid_amount_based_on_received_amount &&
|
||||
(frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency)) {
|
||||
// target exchange rate should always be same as source if both account currencies is same
|
||||
if(frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
|
||||
frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate);
|
||||
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
|
||||
}
|
||||
|
@ -55,14 +55,17 @@ class PaymentEntry(AccountsController):
|
||||
self.validate_mandatory()
|
||||
self.validate_reference_documents()
|
||||
self.set_tax_withholding()
|
||||
self.apply_taxes()
|
||||
self.set_amounts()
|
||||
self.validate_amounts()
|
||||
self.apply_taxes()
|
||||
self.set_amounts_after_tax()
|
||||
self.clear_unallocated_reference_document_rows()
|
||||
self.validate_payment_against_negative_invoice()
|
||||
self.validate_transaction_reference()
|
||||
self.set_title()
|
||||
self.set_remarks()
|
||||
self.validate_duplicate_entry()
|
||||
self.validate_payment_type_with_outstanding()
|
||||
self.validate_allocated_amount()
|
||||
self.validate_paid_invoices()
|
||||
self.ensure_supplier_is_not_blocked()
|
||||
@ -118,6 +121,11 @@ class PaymentEntry(AccountsController):
|
||||
if not self.get(field):
|
||||
self.set(field, bank_data.account)
|
||||
|
||||
def validate_payment_type_with_outstanding(self):
|
||||
total_outstanding = sum(d.allocated_amount for d in self.get('references'))
|
||||
if total_outstanding < 0 and self.party_type == 'Customer' and self.payment_type == 'Receive':
|
||||
frappe.throw(_("Cannot receive from customer against negative outstanding"), title=_("Incorrect Payment Type"))
|
||||
|
||||
def validate_allocated_amount(self):
|
||||
for d in self.get("references"):
|
||||
if (flt(d.allocated_amount))> 0:
|
||||
@ -236,7 +244,9 @@ class PaymentEntry(AccountsController):
|
||||
self.company_currency, self.posting_date)
|
||||
|
||||
def set_target_exchange_rate(self, ref_doc=None):
|
||||
if self.paid_to and not self.target_exchange_rate:
|
||||
if self.paid_from_account_currency == self.paid_to_account_currency:
|
||||
self.target_exchange_rate = self.source_exchange_rate
|
||||
elif self.paid_to and not self.target_exchange_rate:
|
||||
if ref_doc:
|
||||
if self.paid_to_account_currency == ref_doc.currency:
|
||||
self.target_exchange_rate = ref_doc.get("exchange_rate")
|
||||
@ -468,13 +478,22 @@ class PaymentEntry(AccountsController):
|
||||
def set_amounts(self):
|
||||
self.set_received_amount()
|
||||
self.set_amounts_in_company_currency()
|
||||
self.set_amounts_after_tax()
|
||||
self.set_total_allocated_amount()
|
||||
self.set_unallocated_amount()
|
||||
self.set_difference_amount()
|
||||
|
||||
def validate_amounts(self):
|
||||
self.validate_received_amount()
|
||||
|
||||
def validate_received_amount(self):
|
||||
if self.paid_from_account_currency == self.paid_to_account_currency:
|
||||
if self.paid_amount != self.received_amount:
|
||||
frappe.throw(_("Received Amount cannot be greater than Paid Amount"))
|
||||
|
||||
def set_received_amount(self):
|
||||
self.base_received_amount = self.base_paid_amount
|
||||
if self.paid_from_account_currency == self.paid_to_account_currency:
|
||||
self.received_amount = self.paid_amount
|
||||
|
||||
def set_amounts_after_tax(self):
|
||||
applicable_tax = 0
|
||||
|
@ -107,7 +107,7 @@ class TestPaymentEntry(unittest.TestCase):
|
||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
|
||||
pe.reference_no = "1"
|
||||
pe.reference_date = "2016-01-01"
|
||||
pe.target_exchange_rate = 50
|
||||
pe.source_exchange_rate = 50
|
||||
pe.insert()
|
||||
pe.submit()
|
||||
|
||||
@ -154,7 +154,7 @@ class TestPaymentEntry(unittest.TestCase):
|
||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
|
||||
pe.reference_no = "1"
|
||||
pe.reference_date = "2016-01-01"
|
||||
pe.target_exchange_rate = 50
|
||||
pe.source_exchange_rate = 50
|
||||
pe.insert()
|
||||
pe.submit()
|
||||
|
||||
@ -491,7 +491,7 @@ class TestPaymentEntry(unittest.TestCase):
|
||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
|
||||
pe.reference_no = "1"
|
||||
pe.reference_date = "2016-01-01"
|
||||
pe.target_exchange_rate = 55
|
||||
pe.source_exchange_rate = 55
|
||||
|
||||
pe.append("deductions", {
|
||||
"account": "_Test Exchange Gain/Loss - _TC",
|
||||
|
@ -50,9 +50,13 @@ class PeriodClosingVoucher(AccountsController):
|
||||
.format(pce[0][0], self.posting_date))
|
||||
|
||||
def make_gl_entries(self):
|
||||
gl_entries = []
|
||||
net_pl_balance = 0
|
||||
gl_entries = self.get_gl_entries()
|
||||
if gl_entries:
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
make_gl_entries(gl_entries)
|
||||
|
||||
def get_gl_entries(self):
|
||||
gl_entries = []
|
||||
pl_accounts = self.get_pl_balances()
|
||||
|
||||
for acc in pl_accounts:
|
||||
@ -60,6 +64,7 @@ class PeriodClosingVoucher(AccountsController):
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
"account": acc.account,
|
||||
"cost_center": acc.cost_center,
|
||||
"finance_book": acc.finance_book,
|
||||
"account_currency": acc.account_currency,
|
||||
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) < 0 else 0,
|
||||
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
|
||||
@ -67,35 +72,13 @@ class PeriodClosingVoucher(AccountsController):
|
||||
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0
|
||||
}, item=acc))
|
||||
|
||||
net_pl_balance += flt(acc.bal_in_company_currency)
|
||||
if gl_entries:
|
||||
gle_for_net_pl_bal = self.get_pnl_gl_entry(pl_accounts)
|
||||
gl_entries += gle_for_net_pl_bal
|
||||
|
||||
if net_pl_balance:
|
||||
if self.cost_center_wise_pnl:
|
||||
costcenter_wise_gl_entries = self.get_costcenter_wise_pnl_gl_entries(pl_accounts)
|
||||
gl_entries += costcenter_wise_gl_entries
|
||||
else:
|
||||
gl_entry = self.get_pnl_gl_entry(net_pl_balance)
|
||||
gl_entries.append(gl_entry)
|
||||
return gl_entries
|
||||
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
make_gl_entries(gl_entries)
|
||||
|
||||
def get_pnl_gl_entry(self, net_pl_balance):
|
||||
cost_center = frappe.db.get_value("Company", self.company, "cost_center")
|
||||
gl_entry = self.get_gl_dict({
|
||||
"account": self.closing_account_head,
|
||||
"debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0,
|
||||
"debit": abs(net_pl_balance) if net_pl_balance > 0 else 0,
|
||||
"credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0,
|
||||
"credit": abs(net_pl_balance) if net_pl_balance < 0 else 0,
|
||||
"cost_center": cost_center
|
||||
})
|
||||
|
||||
self.update_default_dimensions(gl_entry)
|
||||
|
||||
return gl_entry
|
||||
|
||||
def get_costcenter_wise_pnl_gl_entries(self, pl_accounts):
|
||||
def get_pnl_gl_entry(self, pl_accounts):
|
||||
company_cost_center = frappe.db.get_value("Company", self.company, "cost_center")
|
||||
gl_entries = []
|
||||
|
||||
@ -104,6 +87,7 @@ class PeriodClosingVoucher(AccountsController):
|
||||
gl_entry = self.get_gl_dict({
|
||||
"account": self.closing_account_head,
|
||||
"cost_center": acc.cost_center or company_cost_center,
|
||||
"finance_book": acc.finance_book,
|
||||
"account_currency": acc.account_currency,
|
||||
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0,
|
||||
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
|
||||
@ -130,7 +114,7 @@ class PeriodClosingVoucher(AccountsController):
|
||||
def get_pl_balances(self):
|
||||
"""Get balance for dimension-wise pl accounts"""
|
||||
|
||||
dimension_fields = ['t1.cost_center']
|
||||
dimension_fields = ['t1.cost_center', 't1.finance_book']
|
||||
|
||||
self.accounting_dimensions = get_accounting_dimensions()
|
||||
for dimension in self.accounting_dimensions:
|
||||
|
@ -8,6 +8,7 @@ import frappe
|
||||
from frappe.utils import flt, today
|
||||
from erpnext.accounts.utils import get_fiscal_year, now
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||
from erpnext.accounts.doctype.finance_book.test_finance_book import create_finance_book
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
|
||||
class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
@ -118,6 +119,58 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
|
||||
self.assertTrue(pcv_gle, expected_gle)
|
||||
|
||||
def test_period_closing_with_finance_book_entries(self):
|
||||
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
|
||||
|
||||
company = create_company()
|
||||
surplus_account = create_account()
|
||||
cost_center = create_cost_center("Test Cost Center 1")
|
||||
|
||||
create_sales_invoice(
|
||||
company=company,
|
||||
income_account="Sales - TPC",
|
||||
expense_account="Cost of Goods Sold - TPC",
|
||||
cost_center=cost_center,
|
||||
rate=400,
|
||||
debit_to="Debtors - TPC"
|
||||
)
|
||||
jv = make_journal_entry(
|
||||
account1="Cash - TPC",
|
||||
account2="Sales - TPC",
|
||||
amount=400,
|
||||
cost_center=cost_center,
|
||||
posting_date=now()
|
||||
)
|
||||
jv.company = company
|
||||
jv.finance_book = create_finance_book().name
|
||||
jv.save()
|
||||
jv.submit()
|
||||
|
||||
pcv = frappe.get_doc({
|
||||
"transaction_date": today(),
|
||||
"posting_date": today(),
|
||||
"fiscal_year": get_fiscal_year(today())[0],
|
||||
"company": company,
|
||||
"closing_account_head": surplus_account,
|
||||
"remarks": "Test",
|
||||
"doctype": "Period Closing Voucher"
|
||||
})
|
||||
pcv.insert()
|
||||
pcv.submit()
|
||||
|
||||
expected_gle = (
|
||||
(surplus_account, 0.0, 400.0, ''),
|
||||
(surplus_account, 0.0, 400.0, jv.finance_book),
|
||||
('Sales - TPC', 400.0, 0.0, ''),
|
||||
('Sales - TPC', 400.0, 0.0, jv.finance_book)
|
||||
)
|
||||
|
||||
pcv_gle = frappe.db.sql("""
|
||||
select account, debit, credit, finance_book from `tabGL Entry` where voucher_no=%s
|
||||
""", (pcv.name))
|
||||
|
||||
self.assertTrue(pcv_gle, expected_gle)
|
||||
|
||||
def make_period_closing_voucher(self):
|
||||
pcv = frappe.get_doc({
|
||||
"doctype": "Period Closing Voucher",
|
||||
|
@ -85,9 +85,15 @@ class TestPOSClosingEntry(unittest.TestCase):
|
||||
|
||||
pcv_doc.load_from_db()
|
||||
pcv_doc.cancel()
|
||||
si_doc.load_from_db()
|
||||
|
||||
cancelled_invoice = frappe.db.get_value(
|
||||
'POS Invoice Merge Log', {'pos_closing_entry': pcv_doc.name},
|
||||
'consolidated_invoice'
|
||||
)
|
||||
docstatus = frappe.db.get_value("Sales Invoice", cancelled_invoice, 'docstatus')
|
||||
self.assertEqual(docstatus, 2)
|
||||
|
||||
pos_inv1.load_from_db()
|
||||
self.assertEqual(si_doc.docstatus, 2)
|
||||
self.assertEqual(pos_inv1.status, 'Paid')
|
||||
|
||||
|
||||
|
@ -16,7 +16,7 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
|
||||
|
||||
onload(doc) {
|
||||
super.onload();
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log'];
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log', 'POS Closing Entry'];
|
||||
if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
|
||||
this.frm.script_manager.trigger("is_pos");
|
||||
this.frm.refresh_fields();
|
||||
@ -111,16 +111,12 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
|
||||
}
|
||||
|
||||
write_off_outstanding_amount_automatically() {
|
||||
if(cint(this.frm.doc.write_off_outstanding_amount_automatically)) {
|
||||
if (cint(this.frm.doc.write_off_outstanding_amount_automatically)) {
|
||||
frappe.model.round_floats_in(this.frm.doc, ["grand_total", "paid_amount"]);
|
||||
// this will make outstanding amount 0
|
||||
this.frm.set_value("write_off_amount",
|
||||
flt(this.frm.doc.grand_total - this.frm.doc.paid_amount - this.frm.doc.total_advance, precision("write_off_amount"))
|
||||
);
|
||||
this.frm.toggle_enable("write_off_amount", false);
|
||||
|
||||
} else {
|
||||
this.frm.toggle_enable("write_off_amount", true);
|
||||
}
|
||||
|
||||
this.calculate_outstanding_amount(false);
|
||||
|
@ -99,6 +99,7 @@
|
||||
"loyalty_redemption_account",
|
||||
"loyalty_redemption_cost_center",
|
||||
"section_break_49",
|
||||
"coupon_code",
|
||||
"apply_discount_on",
|
||||
"base_discount_amount",
|
||||
"column_break_51",
|
||||
@ -595,7 +596,8 @@
|
||||
{
|
||||
"fieldname": "scan_barcode",
|
||||
"fieldtype": "Data",
|
||||
"label": "Scan Barcode"
|
||||
"label": "Scan Barcode",
|
||||
"options": "Barcode"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 1,
|
||||
@ -1182,7 +1184,8 @@
|
||||
"label": "Write Off Amount",
|
||||
"no_copy": 1,
|
||||
"options": "currency",
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"read_only_depends_on": "eval: doc.write_off_outstanding_amount_automatically"
|
||||
},
|
||||
{
|
||||
"fieldname": "base_write_off_amount",
|
||||
@ -1548,12 +1551,20 @@
|
||||
"no_copy": 1,
|
||||
"options": "Sales Invoice",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "coupon_code",
|
||||
"fieldname": "coupon_code",
|
||||
"fieldtype": "Link",
|
||||
"label": "Coupon Code",
|
||||
"options": "Coupon Code",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-29 13:37:20.636171",
|
||||
"modified": "2021-08-24 18:19:20.728433",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
|
@ -44,6 +44,9 @@ class POSInvoice(SalesInvoice):
|
||||
self.validate_pos()
|
||||
self.validate_payment_amount()
|
||||
self.validate_loyalty_transaction()
|
||||
if self.coupon_code:
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
|
||||
validate_coupon_code(self.coupon_code)
|
||||
|
||||
def on_submit(self):
|
||||
# create the loyalty point ledger entry if the customer is enrolled in any loyalty program
|
||||
@ -58,6 +61,10 @@ class POSInvoice(SalesInvoice):
|
||||
self.check_phone_payments()
|
||||
self.set_status(update=True)
|
||||
|
||||
if self.coupon_code:
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import update_coupon_code_count
|
||||
update_coupon_code_count(self.coupon_code,'used')
|
||||
|
||||
def before_cancel(self):
|
||||
if self.consolidated_invoice and frappe.db.get_value('Sales Invoice', self.consolidated_invoice, 'docstatus') == 1:
|
||||
pos_closing_entry = frappe.get_all(
|
||||
@ -84,6 +91,10 @@ class POSInvoice(SalesInvoice):
|
||||
against_psi_doc.delete_loyalty_point_entry()
|
||||
against_psi_doc.make_loyalty_point_entry()
|
||||
|
||||
if self.coupon_code:
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import update_coupon_code_count
|
||||
update_coupon_code_count(self.coupon_code,'cancelled')
|
||||
|
||||
def check_phone_payments(self):
|
||||
for pay in self.payments:
|
||||
if pay.type == "Phone" and pay.amount >= 0:
|
||||
@ -127,7 +138,7 @@ class POSInvoice(SalesInvoice):
|
||||
.format(item.idx, bold_delivered_serial_nos), title=_("Item Unavailable"))
|
||||
|
||||
def validate_stock_availablility(self):
|
||||
if self.is_return:
|
||||
if self.is_return or self.docstatus != 1:
|
||||
return
|
||||
|
||||
allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
|
||||
|
@ -320,7 +320,8 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
pos2.get("items")[0].serial_no = serial_nos[0]
|
||||
pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pos2.insert)
|
||||
pos2.insert()
|
||||
self.assertRaises(frappe.ValidationError, pos2.submit)
|
||||
|
||||
def test_delivered_serialized_item_transaction(self):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
@ -348,7 +349,8 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
pos2.get("items")[0].serial_no = serial_nos[0]
|
||||
pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pos2.insert)
|
||||
pos2.insert()
|
||||
self.assertRaises(frappe.ValidationError, pos2.submit)
|
||||
|
||||
def test_loyalty_points(self):
|
||||
from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records
|
||||
|
@ -147,4 +147,3 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
|
@ -198,12 +198,19 @@ def apply_pricing_rule(args, doc=None):
|
||||
set_serial_nos_based_on_fifo = frappe.db.get_single_value("Stock Settings",
|
||||
"automatically_set_serial_nos_based_on_fifo")
|
||||
|
||||
item_code_list = tuple(item.get('item_code') for item in item_list)
|
||||
query_items = frappe.get_all('Item', fields=['item_code','has_serial_no'], filters=[['item_code','in',item_code_list]],as_list=1)
|
||||
serialized_items = dict()
|
||||
for item_code, val in query_items:
|
||||
serialized_items.setdefault(item_code, val)
|
||||
|
||||
for item in item_list:
|
||||
args_copy = copy.deepcopy(args)
|
||||
args_copy.update(item)
|
||||
data = get_pricing_rule_for_item(args_copy, item.get('price_list_rate'), doc=doc)
|
||||
out.append(data)
|
||||
if not item.get("serial_no") and set_serial_nos_based_on_fifo and not args.get('is_return'):
|
||||
|
||||
if serialized_items.get(item.get('item_code')) and not item.get("serial_no") and set_serial_nos_based_on_fifo and not args.get('is_return'):
|
||||
out[0].update(get_serial_no_for_item(args_copy))
|
||||
|
||||
return out
|
||||
|
@ -26,4 +26,3 @@ QUnit.test("test pricing rule", function(assert) {
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -106,7 +106,6 @@
|
||||
"depends_on": "eval:doc.rate_or_discount==\"Rate\"",
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Rate"
|
||||
},
|
||||
{
|
||||
@ -170,7 +169,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-07 11:56:23.424137",
|
||||
"modified": "2021-08-19 15:49:29.598727",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Promotional Scheme Price Discount",
|
||||
|
@ -668,8 +668,7 @@
|
||||
"fieldname": "scan_barcode",
|
||||
"fieldtype": "Data",
|
||||
"label": "Scan Barcode",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Barcode"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 1,
|
||||
@ -1715,7 +1714,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-07 17:53:14.351439",
|
||||
"modified": "2021-08-17 20:16:12.737743",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
@ -72,4 +72,3 @@ QUnit.test("test purchase invoice", function(assert) {
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -26,4 +26,3 @@ QUnit.test("test sales taxes and charges template", function(assert) {
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
{% include "erpnext/regional/india/taxes.js" %}
|
||||
{% include "erpnext/regional/india/e_invoice/einvoice.js" %}
|
||||
|
||||
erpnext.setup_auto_gst_taxation('Sales Invoice');
|
||||
erpnext.setup_einvoice_actions('Sales Invoice')
|
||||
|
||||
frappe.ui.form.on("Sales Invoice", {
|
||||
setup: function(frm) {
|
||||
|
@ -36,139 +36,4 @@ frappe.listview_settings['Sales Invoice'].onload = function (list_view) {
|
||||
};
|
||||
|
||||
list_view.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false);
|
||||
|
||||
const generate_irns = () => {
|
||||
const docnames = list_view.get_checked_items(true);
|
||||
if (docnames && docnames.length) {
|
||||
frappe.call({
|
||||
method: 'erpnext.regional.india.e_invoice.utils.generate_einvoices',
|
||||
args: { docnames },
|
||||
freeze: true,
|
||||
freeze_message: __('Generating E-Invoices...')
|
||||
});
|
||||
} else {
|
||||
frappe.msgprint({
|
||||
message: __('Please select at least one sales invoice to generate IRN'),
|
||||
title: __('No Invoice Selected'),
|
||||
indicator: 'red'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const cancel_irns = () => {
|
||||
const docnames = list_view.get_checked_items(true);
|
||||
|
||||
const fields = [
|
||||
{
|
||||
"label": "Reason",
|
||||
"fieldname": "reason",
|
||||
"fieldtype": "Select",
|
||||
"reqd": 1,
|
||||
"default": "1-Duplicate",
|
||||
"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
|
||||
},
|
||||
{
|
||||
"label": "Remark",
|
||||
"fieldname": "remark",
|
||||
"fieldtype": "Data",
|
||||
"reqd": 1
|
||||
}
|
||||
];
|
||||
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __("Cancel IRN"),
|
||||
fields: fields,
|
||||
primary_action: function() {
|
||||
const data = d.get_values();
|
||||
frappe.call({
|
||||
method: 'erpnext.regional.india.e_invoice.utils.cancel_irns',
|
||||
args: {
|
||||
doctype: list_view.doctype,
|
||||
docnames,
|
||||
reason: data.reason.split('-')[0],
|
||||
remark: data.remark
|
||||
},
|
||||
freeze: true,
|
||||
freeze_message: __('Cancelling E-Invoices...'),
|
||||
});
|
||||
d.hide();
|
||||
},
|
||||
primary_action_label: __('Submit')
|
||||
});
|
||||
d.show();
|
||||
};
|
||||
|
||||
let einvoicing_enabled = false;
|
||||
frappe.db.get_single_value("E Invoice Settings", "enable").then(enabled => {
|
||||
einvoicing_enabled = enabled;
|
||||
});
|
||||
|
||||
list_view.$result.on("change", "input[type=checkbox]", () => {
|
||||
if (einvoicing_enabled) {
|
||||
const docnames = list_view.get_checked_items(true);
|
||||
// show/hide e-invoicing actions when no sales invoices are checked
|
||||
if (docnames && docnames.length) {
|
||||
// prevent adding actions twice if e-invoicing action group already exists
|
||||
if (list_view.page.get_inner_group_button(__('E-Invoicing')).length == 0) {
|
||||
list_view.page.add_inner_button(__('Generate IRNs'), generate_irns, __('E-Invoicing'));
|
||||
list_view.page.add_inner_button(__('Cancel IRNs'), cancel_irns, __('E-Invoicing'));
|
||||
}
|
||||
} else {
|
||||
list_view.page.remove_inner_button(__('Generate IRNs'), __('E-Invoicing'));
|
||||
list_view.page.remove_inner_button(__('Cancel IRNs'), __('E-Invoicing'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.realtime.on("bulk_einvoice_generation_complete", (data) => {
|
||||
const { failures, user, invoices } = data;
|
||||
|
||||
if (invoices.length != failures.length) {
|
||||
frappe.msgprint({
|
||||
message: __('{0} e-invoices generated successfully', [invoices.length]),
|
||||
title: __('Bulk E-Invoice Generation Complete'),
|
||||
indicator: 'orange'
|
||||
});
|
||||
}
|
||||
|
||||
if (failures && failures.length && user == frappe.session.user) {
|
||||
let message = `
|
||||
Failed to generate IRNs for following ${failures.length} sales invoices:
|
||||
<ul style="padding-left: 20px; padding-top: 5px;">
|
||||
${failures.map(d => `<li>${d.docname}</li>`).join('')}
|
||||
</ul>
|
||||
`;
|
||||
frappe.msgprint({
|
||||
message: message,
|
||||
title: __('Bulk E-Invoice Generation Complete'),
|
||||
indicator: 'orange'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
frappe.realtime.on("bulk_einvoice_cancellation_complete", (data) => {
|
||||
const { failures, user, invoices } = data;
|
||||
|
||||
if (invoices.length != failures.length) {
|
||||
frappe.msgprint({
|
||||
message: __('{0} e-invoices cancelled successfully', [invoices.length]),
|
||||
title: __('Bulk E-Invoice Cancellation Complete'),
|
||||
indicator: 'orange'
|
||||
});
|
||||
}
|
||||
|
||||
if (failures && failures.length && user == frappe.session.user) {
|
||||
let message = `
|
||||
Failed to cancel IRNs for following ${failures.length} sales invoices:
|
||||
<ul style="padding-left: 20px; padding-top: 5px;">
|
||||
${failures.map(d => `<li>${d.docname}</li>`).join('')}
|
||||
</ul>
|
||||
`;
|
||||
frappe.msgprint({
|
||||
message: message,
|
||||
title: __('Bulk E-Invoice Cancellation Complete'),
|
||||
indicator: 'orange'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
@ -154,9 +154,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
||||
return
|
||||
}
|
||||
|
||||
$.each(doc["items"], function(i, row) {
|
||||
doc.items.forEach((row) => {
|
||||
if(row.delivery_note) frappe.model.clear_doc("Delivery Note", row.delivery_note)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
set_default_print_format() {
|
||||
@ -324,16 +324,12 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
||||
}
|
||||
|
||||
write_off_outstanding_amount_automatically() {
|
||||
if(cint(this.frm.doc.write_off_outstanding_amount_automatically)) {
|
||||
if (cint(this.frm.doc.write_off_outstanding_amount_automatically)) {
|
||||
frappe.model.round_floats_in(this.frm.doc, ["grand_total", "paid_amount"]);
|
||||
// this will make outstanding amount 0
|
||||
this.frm.set_value("write_off_amount",
|
||||
flt(this.frm.doc.grand_total - this.frm.doc.paid_amount - this.frm.doc.total_advance, precision("write_off_amount"))
|
||||
);
|
||||
this.frm.toggle_enable("write_off_amount", false);
|
||||
|
||||
} else {
|
||||
this.frm.toggle_enable("write_off_amount", true);
|
||||
}
|
||||
|
||||
this.calculate_outstanding_amount(false);
|
||||
@ -457,6 +453,18 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
||||
});
|
||||
calculate_total_billing_amount(cur_frm)
|
||||
}
|
||||
|
||||
currency() {
|
||||
var me = this;
|
||||
super.currency();
|
||||
if (this.frm.doc.timesheets) {
|
||||
this.frm.doc.timesheets.forEach((d) => {
|
||||
let row = frappe.get_doc(d.doctype, d.name)
|
||||
set_timesheet_detail_rate(row.doctype, row.name, me.frm.doc.currency, row.timesheet_detail)
|
||||
});
|
||||
calculate_total_billing_amount(this.frm);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// for backward compatibility: combine new and previous states
|
||||
@ -787,8 +795,6 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']);
|
||||
else hide_field(['c_form_applicable', 'c_form_no']);
|
||||
|
||||
frm.toggle_enable("write_off_amount", !!!cint(doc.write_off_outstanding_amount_automatically));
|
||||
|
||||
frm.refresh_fields();
|
||||
},
|
||||
|
||||
@ -980,9 +986,9 @@ var calculate_total_billing_amount = function(frm) {
|
||||
|
||||
doc.total_billing_amount = 0.0
|
||||
if (doc.timesheets) {
|
||||
$.each(doc.timesheets, function(index, data){
|
||||
doc.total_billing_amount += flt(data.billing_amount)
|
||||
})
|
||||
doc.timesheets.forEach((d) => {
|
||||
doc.total_billing_amount += flt(d.billing_amount)
|
||||
});
|
||||
}
|
||||
|
||||
refresh_field('total_billing_amount')
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -285,8 +285,6 @@ class SalesInvoice(SellingController):
|
||||
|
||||
def before_cancel(self):
|
||||
self.check_if_consolidated_invoice()
|
||||
|
||||
super(SalesInvoice, self).before_cancel()
|
||||
self.update_time_sheet(None)
|
||||
|
||||
def on_cancel(self):
|
||||
|
@ -26,6 +26,7 @@ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
from erpnext.accounts.utils import PaymentEntryUnlinkError
|
||||
|
||||
class TestSalesInvoice(unittest.TestCase):
|
||||
def make(self):
|
||||
@ -136,7 +137,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
pe.paid_to_account_currency = si.currency
|
||||
pe.source_exchange_rate = 1
|
||||
pe.target_exchange_rate = 1
|
||||
pe.paid_amount = si.grand_total
|
||||
pe.paid_amount = si.outstanding_amount
|
||||
pe.insert()
|
||||
pe.submit()
|
||||
|
||||
@ -145,6 +146,42 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertRaises(frappe.LinkExistsError, si.cancel)
|
||||
unlink_payment_on_cancel_of_invoice()
|
||||
|
||||
def test_payment_entry_unlink_against_standalone_credit_note(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
si1 = create_sales_invoice(rate=1000)
|
||||
si2 = create_sales_invoice(rate=300)
|
||||
si3 = create_sales_invoice(qty=-1, rate=300, is_return=1)
|
||||
|
||||
|
||||
pe = get_payment_entry("Sales Invoice", si1.name, bank_account="_Test Bank - _TC")
|
||||
pe.append('references', {
|
||||
'reference_doctype': 'Sales Invoice',
|
||||
'reference_name': si2.name,
|
||||
'total_amount': si2.grand_total,
|
||||
'outstanding_amount': si2.outstanding_amount,
|
||||
'allocated_amount': si2.outstanding_amount
|
||||
})
|
||||
|
||||
pe.append('references', {
|
||||
'reference_doctype': 'Sales Invoice',
|
||||
'reference_name': si3.name,
|
||||
'total_amount': si3.grand_total,
|
||||
'outstanding_amount': si3.outstanding_amount,
|
||||
'allocated_amount': si3.outstanding_amount
|
||||
})
|
||||
|
||||
pe.reference_no = 'Test001'
|
||||
pe.reference_date = nowdate()
|
||||
pe.save()
|
||||
pe.submit()
|
||||
|
||||
si2.load_from_db()
|
||||
si2.cancel()
|
||||
|
||||
si1.load_from_db()
|
||||
self.assertRaises(PaymentEntryUnlinkError, si1.cancel)
|
||||
|
||||
|
||||
def test_sales_invoice_calculation_export_currency(self):
|
||||
si = frappe.copy_doc(test_records[2])
|
||||
si.currency = "USD"
|
||||
@ -2014,7 +2051,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
data = get_ewb_data("Sales Invoice", [si.name])
|
||||
|
||||
self.assertEqual(data['version'], '1.0.1118')
|
||||
self.assertEqual(data['version'], '1.0.0421')
|
||||
self.assertEqual(data['billLists'][0]['fromGstin'], '27AAECE4835E1ZR')
|
||||
self.assertEqual(data['billLists'][0]['fromTrdName'], '_Test Company')
|
||||
self.assertEqual(data['billLists'][0]['toTrdName'], '_Test Customer')
|
||||
@ -2027,54 +2064,6 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(data['billLists'][0]['actualFromStateCode'],7)
|
||||
self.assertEqual(data['billLists'][0]['fromStateCode'],27)
|
||||
|
||||
def test_einvoice_submission_without_irn(self):
|
||||
# init
|
||||
einvoice_settings = frappe.get_doc('E Invoice Settings')
|
||||
einvoice_settings.enable = 1
|
||||
einvoice_settings.applicable_from = nowdate()
|
||||
einvoice_settings.append('credentials', {
|
||||
'company': '_Test Company',
|
||||
'gstin': '27AAECE4835E1ZR',
|
||||
'username': 'test',
|
||||
'password': 'test'
|
||||
})
|
||||
einvoice_settings.save()
|
||||
|
||||
country = frappe.flags.country
|
||||
frappe.flags.country = 'India'
|
||||
|
||||
si = make_sales_invoice_for_ewaybill()
|
||||
self.assertRaises(frappe.ValidationError, si.submit)
|
||||
|
||||
si.irn = 'test_irn'
|
||||
si.submit()
|
||||
|
||||
# reset
|
||||
einvoice_settings = frappe.get_doc('E Invoice Settings')
|
||||
einvoice_settings.enable = 0
|
||||
frappe.flags.country = country
|
||||
|
||||
def test_einvoice_json(self):
|
||||
from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals
|
||||
|
||||
si = get_sales_invoice_for_e_invoice()
|
||||
si.discount_amount = 100
|
||||
si.save()
|
||||
|
||||
einvoice = make_einvoice(si)
|
||||
self.assertTrue(einvoice['EwbDtls'])
|
||||
validate_totals(einvoice)
|
||||
|
||||
si.apply_discount_on = 'Net Total'
|
||||
si.save()
|
||||
einvoice = make_einvoice(si)
|
||||
validate_totals(einvoice)
|
||||
|
||||
[d.set('included_in_print_rate', 1) for d in si.taxes]
|
||||
si.save()
|
||||
einvoice = make_einvoice(si)
|
||||
validate_totals(einvoice)
|
||||
|
||||
def test_item_tax_net_range(self):
|
||||
item = create_item("T Shirt")
|
||||
|
||||
|
@ -40,4 +40,3 @@ QUnit.test("test sales Invoice", function(assert) {
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -33,4 +33,3 @@ QUnit.test("test sales invoice with margin", function(assert) {
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -54,4 +54,3 @@ QUnit.test("test sales Invoice with payment", function(assert) {
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -49,4 +49,3 @@ QUnit.test("test sales Invoice with payment request", function(assert) {
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -42,4 +42,3 @@ QUnit.test("test sales Invoice with serialize item", function(assert) {
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -26,4 +26,3 @@ QUnit.test("test sales taxes and charges template", function(assert) {
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -34,4 +34,3 @@ QUnit.test("test Shipping Rule", function(assert) {
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -34,4 +34,3 @@ QUnit.test("test Shipping Rule", function(assert) {
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -630,5 +630,3 @@ class TestSubscription(unittest.TestCase):
|
||||
|
||||
subscription.process()
|
||||
self.assertEqual(len(subscription.invoices), 1)
|
||||
|
||||
|
||||
|
@ -101,7 +101,7 @@ def merge_similar_entries(gl_map, precision=None):
|
||||
|
||||
def check_if_in_list(gle, gl_map, dimensions=None):
|
||||
account_head_fieldnames = ['voucher_detail_no', 'party', 'against_voucher',
|
||||
'cost_center', 'against_voucher_type', 'party_type', 'project']
|
||||
'cost_center', 'against_voucher_type', 'party_type', 'project', 'finance_book']
|
||||
|
||||
if dimensions:
|
||||
account_head_fieldnames = account_head_fieldnames + dimensions
|
||||
|
@ -286,6 +286,7 @@ def validate_party_gle_currency(party_type, party, company, party_account_curren
|
||||
.format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company)), InvalidAccountCurrency)
|
||||
|
||||
def validate_party_accounts(doc):
|
||||
|
||||
companies = []
|
||||
|
||||
for account in doc.get("accounts"):
|
||||
@ -446,6 +447,10 @@ def get_payment_terms_template(party_name, party_type, company=None):
|
||||
return template
|
||||
|
||||
def validate_party_frozen_disabled(party_type, party_name):
|
||||
|
||||
if frappe.flags.ignore_party_validation:
|
||||
return
|
||||
|
||||
if party_type and party_name:
|
||||
if party_type in ("Customer", "Supplier"):
|
||||
party = frappe.get_cached_value(party_type, party_name, ["is_frozen", "disabled"], as_dict=True)
|
||||
|
@ -1,162 +0,0 @@
|
||||
{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%}
|
||||
{%- set einvoice = json.loads(doc.signed_einvoice) -%}
|
||||
|
||||
<div class="page-break">
|
||||
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
|
||||
{% if letter_head and not no_letterhead %}
|
||||
<div class="letter-head">{{ letter_head }}</div>
|
||||
{% endif %}
|
||||
<div class="print-heading">
|
||||
<h2>E Invoice<br><small>{{ doc.name }}</small></h2>
|
||||
</div>
|
||||
</div>
|
||||
{% if print_settings.repeat_header_footer %}
|
||||
<div id="footer-html" class="visible-pdf">
|
||||
{% if not no_letterhead and footer %}
|
||||
<div class="letter-head-footer">
|
||||
{{ footer }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<p class="text-center small page-number visible-pdf">
|
||||
{{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<h5 class="font-bold" style="margin-top: 0px;">1. Transaction Details</h5>
|
||||
<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
|
||||
<div class="col-xs-8 column-break">
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>IRN</label></div>
|
||||
<div class="col-xs-8 value">{{ einvoice.Irn }}</div>
|
||||
</div>
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>Ack. No</label></div>
|
||||
<div class="col-xs-8 value">{{ einvoice.AckNo }}</div>
|
||||
</div>
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>Ack. Date</label></div>
|
||||
<div class="col-xs-8 value">{{ frappe.utils.format_datetime(einvoice.AckDt, "dd/MM/yyyy hh:mm:ss") }}</div>
|
||||
</div>
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>Category</label></div>
|
||||
<div class="col-xs-8 value">{{ einvoice.TranDtls.SupTyp }}</div>
|
||||
</div>
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>Document Type</label></div>
|
||||
<div class="col-xs-8 value">{{ einvoice.DocDtls.Typ }}</div>
|
||||
</div>
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>Document No</label></div>
|
||||
<div class="col-xs-8 value">{{ einvoice.DocDtls.No }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-4 column-break">
|
||||
<img src="{{ doc.qrcode_image }}" width="175px" style="float: right;">
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="font-bold" style="margin-top: 15px; margin-bottom: 10px;">2. Party Details</h5>
|
||||
<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
|
||||
{%- set seller = einvoice.SellerDtls -%}
|
||||
<div class="col-xs-6 column-break">
|
||||
<h5 style="margin-bottom: 5px;">Seller</h5>
|
||||
<p>{{ seller.Gstin }}</p>
|
||||
<p>{{ seller.LglNm }}</p>
|
||||
<p>{{ seller.Addr1 }}</p>
|
||||
{%- if seller.Addr2 -%} <p>{{ seller.Addr2 }}</p> {% endif %}
|
||||
<p>{{ seller.Loc }}</p>
|
||||
<p>{{ frappe.db.get_value("Address", doc.company_address, "gst_state") }} - {{ seller.Pin }}</p>
|
||||
|
||||
{%- if einvoice.ShipDtls -%}
|
||||
{%- set shipping = einvoice.ShipDtls -%}
|
||||
<h5 style="margin-bottom: 5px;">Shipping</h5>
|
||||
<p>{{ shipping.Gstin }}</p>
|
||||
<p>{{ shipping.LglNm }}</p>
|
||||
<p>{{ shipping.Addr1 }}</p>
|
||||
{%- if shipping.Addr2 -%} <p>{{ shipping.Addr2 }}</p> {% endif %}
|
||||
<p>{{ shipping.Loc }}</p>
|
||||
<p>{{ frappe.db.get_value("Address", doc.shipping_address_name, "gst_state") }} - {{ shipping.Pin }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{%- set buyer = einvoice.BuyerDtls -%}
|
||||
<div class="col-xs-6 column-break">
|
||||
<h5 style="margin-bottom: 5px;">Buyer</h5>
|
||||
<p>{{ buyer.Gstin }}</p>
|
||||
<p>{{ buyer.LglNm }}</p>
|
||||
<p>{{ buyer.Addr1 }}</p>
|
||||
{%- if buyer.Addr2 -%} <p>{{ buyer.Addr2 }}</p> {% endif %}
|
||||
<p>{{ buyer.Loc }}</p>
|
||||
<p>{{ frappe.db.get_value("Address", doc.customer_address, "gst_state") }} - {{ buyer.Pin }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="overflow-x: auto;">
|
||||
<h5 class="font-bold" style="margin-top: 15px; margin-bottom: 10px;">3. Item Details</h5>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left" style="width: 3%;">Sr. No.</th>
|
||||
<th class="text-left">Item</th>
|
||||
<th class="text-left" style="width: 10%;">HSN Code</th>
|
||||
<th class="text-left" style="width: 5%;">Qty</th>
|
||||
<th class="text-left" style="width: 5%;">UOM</th>
|
||||
<th class="text-left">Rate</th>
|
||||
<th class="text-left" style="width: 5%;">Discount</th>
|
||||
<th class="text-left">Taxable Amount</th>
|
||||
<th class="text-left" style="width: 7%;">Tax Rate</th>
|
||||
<th class="text-left" style="width: 5%;">Other Charges</th>
|
||||
<th class="text-left">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in einvoice.ItemList %}
|
||||
<tr>
|
||||
<td class="text-left" style="width: 3%;">{{ item.SlNo }}</td>
|
||||
<td class="text-left">{{ item.PrdDesc }}</td>
|
||||
<td class="text-left" style="width: 10%;">{{ item.HsnCd }}</td>
|
||||
<td class="text-right" style="width: 5%;">{{ item.Qty }}</td>
|
||||
<td class="text-left" style="width: 5%;">{{ item.Unit }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(item.UnitPrice, None, "INR") }}</td>
|
||||
<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(item.Discount, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(item.AssAmt, None, "INR") }}</td>
|
||||
<td class="text-right" style="width: 7%;">{{ item.GstRt + item.CesRt }} %</td>
|
||||
<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(item.TotItemVal, None, "INR") }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="overflow-x: auto;">
|
||||
<h5 class="font-bold" style="margin-bottom: 0px;">4. Value Details</h5>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">Taxable Amount</th>
|
||||
<th class="text-left">CGST</th>
|
||||
<th class="text-left"">SGST</th>
|
||||
<th class="text-left">IGST</th>
|
||||
<th class="text-left">CESS</th>
|
||||
<th class="text-left" style="width: 10%;">State CESS</th>
|
||||
<th class="text-left">Discount</th>
|
||||
<th class="text-left" style="width: 10%;">Other Charges</th>
|
||||
<th class="text-left" style="width: 10%;">Round Off</th>
|
||||
<th class="text-left">Total Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{%- set value_details = einvoice.ValDtls -%}
|
||||
<tr>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.AssVal, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CgstVal, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.SgstVal, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.IgstVal, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.OthChrg, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
@ -1,24 +0,0 @@
|
||||
{
|
||||
"align_labels_right": 1,
|
||||
"creation": "2020-10-10 18:01:21.032914",
|
||||
"custom_format": 0,
|
||||
"default_print_language": "en-US",
|
||||
"disabled": 1,
|
||||
"doc_type": "Sales Invoice",
|
||||
"docstatus": 0,
|
||||
"doctype": "Print Format",
|
||||
"font": "Default",
|
||||
"html": "",
|
||||
"idx": 0,
|
||||
"line_breaks": 1,
|
||||
"modified": "2020-10-23 19:54:40.634936",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "GST E-Invoice",
|
||||
"owner": "Administrator",
|
||||
"print_format_builder": 0,
|
||||
"print_format_type": "Jinja",
|
||||
"raw_printing": 0,
|
||||
"show_section_headings": 1,
|
||||
"standard": "Yes"
|
||||
}
|
@ -27,4 +27,3 @@
|
||||
{{ _("Authorized Signatory") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -62,8 +62,3 @@ def make_sales_invoice():
|
||||
income_account = 'Sales - _TC2',
|
||||
expense_account = 'Cost of Goods Sold - _TC2',
|
||||
cost_center = 'Main - _TC2')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -136,4 +136,3 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
}
|
||||
|
||||
erpnext.utils.add_dimensions('Accounts Payable', 9);
|
||||
|
||||
|
@ -105,4 +105,3 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
||||
}
|
||||
|
||||
erpnext.utils.add_dimensions('Accounts Payable Summary', 9);
|
||||
|
||||
|
@ -12,4 +12,3 @@ def execute(filters=None):
|
||||
"naming_by": ["Buying Settings", "supp_master_name"],
|
||||
}
|
||||
return AccountsReceivableSummary(filters).run(args)
|
||||
|
||||
|
@ -200,4 +200,3 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
}
|
||||
|
||||
erpnext.utils.add_dimensions('Accounts Receivable', 9);
|
||||
|
||||
|
@ -535,6 +535,8 @@ class ReceivablePayableReport(object):
|
||||
if getdate(entry_date) > getdate(self.filters.report_date):
|
||||
row.range1 = row.range2 = row.range3 = row.range4 = row.range5 = 0.0
|
||||
|
||||
row.total_due = row.range1 + row.range2 + row.range3 + row.range4 + row.range5
|
||||
|
||||
def get_ageing_data(self, entry_date, row):
|
||||
# [0-30, 30-60, 60-90, 90-120, 120-above]
|
||||
row.range1 = row.range2 = row.range3 = row.range4 = row.range5 = 0.0
|
||||
|
@ -93,4 +93,3 @@ def make_credit_note(docname):
|
||||
cost_center = 'Main - _TC2',
|
||||
is_return = 1,
|
||||
return_against = docname)
|
||||
|
||||
|
@ -82,6 +82,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
||||
"range3": 0.0,
|
||||
"range4": 0.0,
|
||||
"range5": 0.0,
|
||||
"total_due": 0.0,
|
||||
"sales_person": []
|
||||
}))
|
||||
|
||||
@ -135,3 +136,6 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
||||
"{range3}-{range4}".format(range3=cint(self.filters["range3"])+ 1, range4=self.filters["range4"]),
|
||||
"{range4}-{above}".format(range4=cint(self.filters["range4"])+ 1, above=_("Above"))]):
|
||||
self.add_column(label=label, fieldname='range' + str(i+1))
|
||||
|
||||
# Add column for total due amount
|
||||
self.add_column(label="Total Amount Due", fieldname='total_due')
|
||||
|
@ -92,4 +92,3 @@ frappe.query_reports["Budget Variance Report"] = {
|
||||
erpnext.dimension_filters.forEach((dimension) => {
|
||||
frappe.query_reports["Budget Variance Report"].filters[4].options.push(dimension["document_type"]);
|
||||
});
|
||||
|
||||
|
@ -399,4 +399,3 @@ def get_chart_data(filters, columns, data):
|
||||
},
|
||||
'type' : 'bar'
|
||||
}
|
||||
|
||||
|
@ -210,10 +210,10 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i
|
||||
company_currency = get_company_currency(filters)
|
||||
|
||||
if filters.filter_based_on == 'Fiscal Year':
|
||||
start_date = fiscal_year.year_start_date
|
||||
start_date = fiscal_year.year_start_date if filters.report != 'Balance Sheet' else None
|
||||
end_date = fiscal_year.year_end_date
|
||||
else:
|
||||
start_date = filters.period_start_date
|
||||
start_date = filters.period_start_date if filters.report != 'Balance Sheet' else None
|
||||
end_date = filters.period_end_date
|
||||
|
||||
gl_entries_by_account = {}
|
||||
|
@ -176,4 +176,3 @@ frappe.query_reports["General Ledger"] = {
|
||||
}
|
||||
|
||||
erpnext.utils.add_dimensions('General Ledger', 15)
|
||||
|
||||
|
@ -1,16 +1,20 @@
|
||||
{
|
||||
"add_total_row": 1,
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2013-02-25 17:03:34",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 3,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2020-08-13 11:26:39.112352",
|
||||
"modified": "2021-08-19 18:57:07.468202",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Gross Profit",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Sales Invoice",
|
||||
"report_name": "Gross Profit",
|
||||
"report_type": "Script Report",
|
||||
|
@ -61,14 +61,15 @@ def execute(filters=None):
|
||||
data.append(row)
|
||||
|
||||
else:
|
||||
for src in gross_profit_data.grouped_data:
|
||||
row = []
|
||||
row.append(filters.currency)
|
||||
for idx, src in enumerate(gross_profit_data.grouped_data):
|
||||
row = []
|
||||
for col in group_wise_columns.get(scrub(filters.group_by)):
|
||||
row.append(src.get(col))
|
||||
|
||||
for col in group_wise_columns.get(scrub(filters.group_by)):
|
||||
row.append(src.get(col))
|
||||
|
||||
data.append(row)
|
||||
row.append(filters.currency)
|
||||
if idx == len(gross_profit_data.grouped_data)-1:
|
||||
row[0] = frappe.bold("Total")
|
||||
data.append(row)
|
||||
|
||||
return columns, data
|
||||
|
||||
@ -214,6 +215,15 @@ class GrossProfitGenerator(object):
|
||||
|
||||
def get_average_rate_based_on_group_by(self):
|
||||
# sum buying / selling totals for group
|
||||
self.totals = frappe._dict(
|
||||
qty=0,
|
||||
base_amount=0,
|
||||
buying_amount=0,
|
||||
gross_profit=0,
|
||||
gross_profit_percent=0,
|
||||
base_rate=0,
|
||||
buying_rate=0
|
||||
)
|
||||
for key in list(self.grouped):
|
||||
if self.filters.get("group_by") != "Invoice":
|
||||
for i, row in enumerate(self.grouped[key]):
|
||||
@ -225,6 +235,7 @@ class GrossProfitGenerator(object):
|
||||
new_row.base_amount += flt(row.base_amount, self.currency_precision)
|
||||
new_row = self.set_average_rate(new_row)
|
||||
self.grouped_data.append(new_row)
|
||||
self.add_to_totals(new_row)
|
||||
else:
|
||||
for i, row in enumerate(self.grouped[key]):
|
||||
if row.parent in self.returned_invoices \
|
||||
@ -237,18 +248,28 @@ class GrossProfitGenerator(object):
|
||||
if (flt(row.qty) or row.base_amount) and self.is_not_invoice_row(row):
|
||||
row = self.set_average_rate(row)
|
||||
self.grouped_data.append(row)
|
||||
self.add_to_totals(row)
|
||||
self.set_average_gross_profit(self.totals)
|
||||
self.grouped_data.append(self.totals)
|
||||
|
||||
def is_not_invoice_row(self, row):
|
||||
return (self.filters.get("group_by") == "Invoice" and row.indent != 0.0) or self.filters.get("group_by") != "Invoice"
|
||||
|
||||
def set_average_rate(self, new_row):
|
||||
self.set_average_gross_profit(new_row)
|
||||
new_row.buying_rate = flt(new_row.buying_amount / new_row.qty, self.float_precision) if new_row.qty else 0
|
||||
new_row.base_rate = flt(new_row.base_amount / new_row.qty, self.float_precision) if new_row.qty else 0
|
||||
return new_row
|
||||
|
||||
def set_average_gross_profit(self, new_row):
|
||||
new_row.gross_profit = flt(new_row.base_amount - new_row.buying_amount, self.currency_precision)
|
||||
new_row.gross_profit_percent = flt(((new_row.gross_profit / new_row.base_amount) * 100.0), self.currency_precision) \
|
||||
if new_row.base_amount else 0
|
||||
new_row.buying_rate = flt(new_row.buying_amount / flt(new_row.qty), self.float_precision) if flt(new_row.qty) else 0
|
||||
new_row.base_rate = flt(new_row.base_amount / flt(new_row.qty), self.float_precision) if flt(new_row.qty) else 0
|
||||
|
||||
return new_row
|
||||
def add_to_totals(self, new_row):
|
||||
for key in self.totals:
|
||||
if new_row.get(key):
|
||||
self.totals[key] += new_row[key]
|
||||
|
||||
def get_returned_invoice_items(self):
|
||||
returned_invoices = frappe.db.sql("""
|
||||
|
@ -76,7 +76,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
'company': d.company,
|
||||
'sales_order': d.sales_order,
|
||||
'delivery_note': d.delivery_note,
|
||||
'income_account': d.unrealized_profit_loss_account or d.income_account,
|
||||
'income_account': d.unrealized_profit_loss_account if d.is_internal_customer == 1 else d.income_account,
|
||||
'cost_center': d.cost_center,
|
||||
'stock_qty': d.stock_qty,
|
||||
'stock_uom': d.stock_uom
|
||||
@ -380,6 +380,7 @@ def get_items(filters, additional_query_columns):
|
||||
`tabSales Invoice Item`.name, `tabSales Invoice Item`.parent,
|
||||
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
|
||||
`tabSales Invoice`.unrealized_profit_loss_account,
|
||||
`tabSales Invoice`.is_internal_customer,
|
||||
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
|
||||
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
|
||||
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
|
||||
@ -625,7 +626,3 @@ def add_sub_total_row(item, total_row_map, group_by_value, tax_columns):
|
||||
for tax in tax_columns:
|
||||
total_row.setdefault(frappe.scrub(tax + ' Amount'), 0.0)
|
||||
total_row[frappe.scrub(tax + ' Amount')] += flt(item[frappe.scrub(tax + ' Amount')])
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -69,4 +69,3 @@ frappe.query_reports["Sales Register"] = {
|
||||
}
|
||||
|
||||
erpnext.utils.add_dimensions('Sales Register', 7);
|
||||
|
||||
|
@ -84,7 +84,7 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
|
||||
# Add amount in unrealized account
|
||||
for account in unrealized_profit_loss_accounts:
|
||||
row.update({
|
||||
frappe.scrub(account): flt(internal_invoice_map.get((inv.name, account)))
|
||||
frappe.scrub(account+"_unrealized"): flt(internal_invoice_map.get((inv.name, account)))
|
||||
})
|
||||
|
||||
# net total
|
||||
@ -258,6 +258,7 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
|
||||
unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
|
||||
from `tabSales Invoice` where docstatus = 1 and name in (%s)
|
||||
and is_internal_customer = 1
|
||||
and ifnull(unrealized_profit_loss_account, '') != ''
|
||||
order by unrealized_profit_loss_account""" %
|
||||
', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list))
|
||||
@ -284,7 +285,7 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
for account in unrealized_profit_loss_accounts:
|
||||
unrealized_profit_loss_account_columns.append({
|
||||
"label": account,
|
||||
"fieldname": frappe.scrub(account),
|
||||
"fieldname": frappe.scrub(account+"_unrealized"),
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120
|
||||
|
@ -110,6 +110,3 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
|
||||
erpnext.utils.add_dimensions('Trial Balance', 6);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
@ -19,6 +19,7 @@ from erpnext.stock import get_warehouse_account_map
|
||||
|
||||
class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
|
||||
class FiscalYearError(frappe.ValidationError): pass
|
||||
class PaymentEntryUnlinkError(frappe.ValidationError): pass
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_fiscal_year(date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False):
|
||||
@ -350,6 +351,7 @@ def reconcile_against_document(args):
|
||||
# cancel advance entry
|
||||
doc = frappe.get_doc(d.voucher_type, d.voucher_no)
|
||||
|
||||
frappe.flags.ignore_party_validation = True
|
||||
doc.make_gl_entries(cancel=1, adv_adj=1)
|
||||
|
||||
# update ref in advance entry
|
||||
@ -361,6 +363,7 @@ def reconcile_against_document(args):
|
||||
# re-submit advance entry
|
||||
doc = frappe.get_doc(d.voucher_type, d.voucher_no)
|
||||
doc.make_gl_entries(cancel = 0, adv_adj =1)
|
||||
frappe.flags.ignore_party_validation = False
|
||||
|
||||
if d.voucher_type in ('Payment Entry', 'Journal Entry'):
|
||||
doc.update_expense_claim()
|
||||
@ -553,10 +556,16 @@ def remove_ref_doc_link_from_pe(ref_type, ref_no):
|
||||
and docstatus < 2""", (now(), frappe.session.user, ref_type, ref_no))
|
||||
|
||||
for pe in linked_pe:
|
||||
pe_doc = frappe.get_doc("Payment Entry", pe)
|
||||
pe_doc.set_total_allocated_amount()
|
||||
pe_doc.set_unallocated_amount()
|
||||
pe_doc.clear_unallocated_reference_document_rows()
|
||||
try:
|
||||
pe_doc = frappe.get_doc("Payment Entry", pe)
|
||||
pe_doc.set_amounts()
|
||||
pe_doc.clear_unallocated_reference_document_rows()
|
||||
pe_doc.validate_payment_type_with_outstanding()
|
||||
except Exception as e:
|
||||
msg = _("There were issues unlinking payment entry {0}.").format(pe_doc.name)
|
||||
msg += '<br>'
|
||||
msg += _("Please cancel payment entry manually first")
|
||||
frappe.throw(msg, exc=PaymentEntryUnlinkError, title=_("Payment Unlink Error"))
|
||||
|
||||
frappe.db.sql("""update `tabPayment Entry` set total_allocated_amount=%s,
|
||||
base_total_allocated_amount=%s, unallocated_amount=%s, modified=%s, modified_by=%s
|
||||
|
@ -36,4 +36,3 @@ QUnit.test("test: Disease", function (assert) {
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
|
@ -10,4 +10,3 @@ frappe.listview_settings['Asset Repair'] = {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -565,6 +565,7 @@
|
||||
"fieldname": "scan_barcode",
|
||||
"fieldtype": "Data",
|
||||
"label": "Scan Barcode",
|
||||
"options": "Barcode",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
@ -1378,7 +1379,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-30 15:17:53.663648",
|
||||
"modified": "2021-08-17 20:16:12.737743",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
@ -93,5 +93,3 @@ var loadAllStandings = function(frm) {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
@ -128,4 +128,3 @@ valid_scorecard = [
|
||||
"weighting_function":"{total_score} * max( 0, min ( 1 , (12 - {period_number}) / 12) )"
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -109,4 +109,3 @@ def make_supplier_scorecard(source_name, target_doc=None):
|
||||
}, target_doc, post_process, ignore_permissions=True)
|
||||
|
||||
return doc
|
||||
|
||||
|
@ -268,4 +268,3 @@ def get_columns(filters):
|
||||
])
|
||||
|
||||
return columns
|
||||
|
||||
|
@ -102,4 +102,3 @@ def get_linked_material_requests(items):
|
||||
mr_list.append(material_request)
|
||||
|
||||
return mr_list
|
||||
|
||||
|
@ -135,14 +135,9 @@ class AccountsController(TransactionBase):
|
||||
|
||||
validate_regional(self)
|
||||
|
||||
validate_einvoice_fields(self)
|
||||
|
||||
if self.doctype != 'Material Request':
|
||||
apply_pricing_rule_on_transaction(self)
|
||||
|
||||
def before_cancel(self):
|
||||
validate_einvoice_fields(self)
|
||||
|
||||
def on_trash(self):
|
||||
# delete sl and gl entries on deletion of transaction
|
||||
if frappe.db.get_single_value('Accounts Settings', 'delete_linked_ledger_entries'):
|
||||
@ -164,7 +159,8 @@ class AccountsController(TransactionBase):
|
||||
self.set_due_date()
|
||||
self.set_payment_schedule()
|
||||
self.validate_payment_schedule_amount()
|
||||
self.validate_due_date()
|
||||
if not self.get('ignore_default_payment_terms_template'):
|
||||
self.validate_due_date()
|
||||
self.validate_advance_entries()
|
||||
|
||||
def validate_non_invoice_documents_schedule(self):
|
||||
@ -1841,6 +1837,11 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
|
||||
for d in data:
|
||||
new_child_flag = False
|
||||
|
||||
if not d.get("item_code"):
|
||||
# ignore empty rows
|
||||
continue
|
||||
|
||||
if not d.get("docname"):
|
||||
new_child_flag = True
|
||||
check_doc_permissions(parent, 'create')
|
||||
@ -1863,7 +1864,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
qty_unchanged = prev_qty == new_qty
|
||||
uom_unchanged = prev_uom == new_uom
|
||||
conversion_factor_unchanged = prev_con_fac == new_con_fac
|
||||
date_unchanged = prev_date == new_date if prev_date and new_date else False # in case of delivery note etc
|
||||
date_unchanged = prev_date == getdate(new_date) if prev_date and new_date else False # in case of delivery note etc
|
||||
if rate_unchanged and qty_unchanged and conversion_factor_unchanged and uom_unchanged and date_unchanged:
|
||||
continue
|
||||
|
||||
@ -1975,7 +1976,3 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
@erpnext.allow_regional
|
||||
def validate_regional(doc):
|
||||
pass
|
||||
|
||||
@erpnext.allow_regional
|
||||
def validate_einvoice_fields(doc):
|
||||
pass
|
@ -344,4 +344,3 @@ def create_variant_doc_for_quick_entry(template, args):
|
||||
variant.name = variant.item_code
|
||||
validate_item_variant_attributes(variant, args)
|
||||
return variant.as_dict()
|
||||
|
||||
|
@ -526,6 +526,9 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters)
|
||||
if meta.is_tree:
|
||||
query_filters.append(['is_group', '=', 0])
|
||||
|
||||
if meta.has_field('disabled'):
|
||||
query_filters.append(['disabled', '!=', 1])
|
||||
|
||||
if meta.has_field('company'):
|
||||
query_filters.append(['company', '=', filters.get('company')])
|
||||
|
||||
|
@ -329,7 +329,6 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
target_doc.po_detail = source_doc.po_detail
|
||||
target_doc.pr_detail = source_doc.pr_detail
|
||||
target_doc.purchase_invoice_item = source_doc.name
|
||||
target_doc.price_list_rate = 0
|
||||
|
||||
elif doctype == "Delivery Note":
|
||||
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
|
||||
@ -360,7 +359,6 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
else:
|
||||
target_doc.pos_invoice_item = source_doc.name
|
||||
|
||||
target_doc.price_list_rate = 0
|
||||
if default_warehouse_for_sales_return:
|
||||
target_doc.warehouse = default_warehouse_for_sales_return
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import cint, flt, cstr, get_link_to_form, nowtime
|
||||
from frappe import _, throw
|
||||
from frappe import _, bold, throw
|
||||
from erpnext.stock.get_item_details import get_bin_details
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
from erpnext.stock.get_item_details import get_conversion_factor
|
||||
@ -16,7 +16,6 @@ from erpnext.controllers.stock_controller import StockController
|
||||
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
|
||||
|
||||
class SellingController(StockController):
|
||||
|
||||
def get_feed(self):
|
||||
return _("To {0} | {1} {2}").format(self.customer_name, self.currency,
|
||||
self.grand_total)
|
||||
@ -169,39 +168,96 @@ class SellingController(StockController):
|
||||
|
||||
def validate_selling_price(self):
|
||||
def throw_message(idx, item_name, rate, ref_rate_field):
|
||||
bold_net_rate = frappe.bold("net rate")
|
||||
msg = (_("""Row #{}: Selling rate for item {} is lower than its {}. Selling {} should be atleast {}""")
|
||||
.format(idx, frappe.bold(item_name), frappe.bold(ref_rate_field), bold_net_rate, frappe.bold(rate)))
|
||||
msg += "<br><br>"
|
||||
msg += (_("""You can alternatively disable selling price validation in {} to bypass this validation.""")
|
||||
.format(get_link_to_form("Selling Settings", "Selling Settings")))
|
||||
frappe.throw(msg, title=_("Invalid Selling Price"))
|
||||
throw(_("""Row #{0}: Selling rate for item {1} is lower than its {2}.
|
||||
Selling {3} should be atleast {4}.<br><br>Alternatively,
|
||||
you can disable selling price validation in {5} to bypass
|
||||
this validation.""").format(
|
||||
idx,
|
||||
bold(item_name),
|
||||
bold(ref_rate_field),
|
||||
bold("net rate"),
|
||||
bold(rate),
|
||||
get_link_to_form("Selling Settings", "Selling Settings"),
|
||||
), title=_("Invalid Selling Price"))
|
||||
|
||||
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
||||
return
|
||||
if hasattr(self, "is_return") and self.is_return:
|
||||
if (
|
||||
self.get("is_return")
|
||||
or not frappe.db.get_single_value("Selling Settings", "validate_selling_price")
|
||||
):
|
||||
return
|
||||
|
||||
for it in self.get("items"):
|
||||
if not it.item_code:
|
||||
is_internal_customer = self.get('is_internal_customer')
|
||||
valuation_rate_map = {}
|
||||
|
||||
for item in self.items:
|
||||
if not item.item_code:
|
||||
continue
|
||||
|
||||
last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"])
|
||||
last_purchase_rate_in_sales_uom = last_purchase_rate * (it.conversion_factor or 1)
|
||||
if flt(it.base_net_rate) < flt(last_purchase_rate_in_sales_uom):
|
||||
throw_message(it.idx, frappe.bold(it.item_name), last_purchase_rate_in_sales_uom, "last purchase rate")
|
||||
last_purchase_rate, is_stock_item = frappe.get_cached_value(
|
||||
"Item", item.item_code, ("last_purchase_rate", "is_stock_item")
|
||||
)
|
||||
|
||||
last_valuation_rate = frappe.db.sql("""
|
||||
SELECT valuation_rate FROM `tabStock Ledger Entry` WHERE item_code = %s
|
||||
AND warehouse = %s AND valuation_rate > 0
|
||||
ORDER BY posting_date DESC, posting_time DESC, creation DESC LIMIT 1
|
||||
""", (it.item_code, it.warehouse))
|
||||
if last_valuation_rate:
|
||||
last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] * (it.conversion_factor or 1)
|
||||
if is_stock_item and flt(it.base_net_rate) < flt(last_valuation_rate_in_sales_uom) \
|
||||
and not self.get('is_internal_customer'):
|
||||
throw_message(it.idx, frappe.bold(it.item_name), last_valuation_rate_in_sales_uom, "valuation rate")
|
||||
last_purchase_rate_in_sales_uom = (
|
||||
last_purchase_rate * (item.conversion_factor or 1)
|
||||
)
|
||||
|
||||
if flt(item.base_net_rate) < flt(last_purchase_rate_in_sales_uom):
|
||||
throw_message(
|
||||
item.idx,
|
||||
item.item_name,
|
||||
last_purchase_rate_in_sales_uom,
|
||||
"last purchase rate"
|
||||
)
|
||||
|
||||
if is_internal_customer or not is_stock_item:
|
||||
continue
|
||||
|
||||
valuation_rate_map[(item.item_code, item.warehouse)] = None
|
||||
|
||||
if not valuation_rate_map:
|
||||
return
|
||||
|
||||
or_conditions = (
|
||||
f"""(item_code = {frappe.db.escape(valuation_rate[0])}
|
||||
and warehouse = {frappe.db.escape(valuation_rate[1])})"""
|
||||
for valuation_rate in valuation_rate_map
|
||||
)
|
||||
|
||||
valuation_rates = frappe.db.sql(f"""
|
||||
select
|
||||
item_code, warehouse, valuation_rate
|
||||
from
|
||||
`tabBin`
|
||||
where
|
||||
({" or ".join(or_conditions)})
|
||||
and valuation_rate > 0
|
||||
""", as_dict=True)
|
||||
|
||||
for rate in valuation_rates:
|
||||
valuation_rate_map[(rate.item_code, rate.warehouse)] = rate.valuation_rate
|
||||
|
||||
for item in self.items:
|
||||
if not item.item_code:
|
||||
continue
|
||||
|
||||
last_valuation_rate = valuation_rate_map.get(
|
||||
(item.item_code, item.warehouse)
|
||||
)
|
||||
|
||||
if not last_valuation_rate:
|
||||
continue
|
||||
|
||||
last_valuation_rate_in_sales_uom = (
|
||||
last_valuation_rate * (item.conversion_factor or 1)
|
||||
)
|
||||
|
||||
if flt(item.base_net_rate) < flt(last_valuation_rate_in_sales_uom):
|
||||
throw_message(
|
||||
item.idx,
|
||||
item.item_name,
|
||||
last_valuation_rate_in_sales_uom,
|
||||
"valuation rate"
|
||||
)
|
||||
|
||||
def get_item_list(self):
|
||||
il = []
|
||||
|
@ -595,7 +595,8 @@ class calculate_taxes_and_totals(object):
|
||||
self.doc.precision("outstanding_amount"))
|
||||
|
||||
if self.doc.doctype == 'Sales Invoice' and self.doc.get('is_pos') and self.doc.get('is_return'):
|
||||
self.update_paid_amount_for_return(total_amount_to_pay)
|
||||
self.set_total_amount_to_default_mop(total_amount_to_pay)
|
||||
self.calculate_paid_amount()
|
||||
|
||||
def calculate_paid_amount(self):
|
||||
|
||||
@ -675,7 +676,7 @@ class calculate_taxes_and_totals(object):
|
||||
def set_item_wise_tax_breakup(self):
|
||||
self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
|
||||
|
||||
def update_paid_amount_for_return(self, total_amount_to_pay):
|
||||
def set_total_amount_to_default_mop(self, total_amount_to_pay):
|
||||
default_mode_of_payment = frappe.db.get_value('POS Payment Method',
|
||||
{'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1)
|
||||
|
||||
@ -687,8 +688,6 @@ class calculate_taxes_and_totals(object):
|
||||
'default': 1
|
||||
})
|
||||
|
||||
self.calculate_paid_amount()
|
||||
|
||||
def get_itemised_tax_breakup_html(doc):
|
||||
if not doc.taxes:
|
||||
return
|
||||
|
@ -235,4 +235,3 @@ def _get_employee_from_user(user):
|
||||
# frappe.db.exists returns a tuple of a tuple
|
||||
return frappe.get_doc('Employee', employee_docname[0][0])
|
||||
return None
|
||||
|
||||
|
@ -36,7 +36,8 @@ class Lead(SellingController):
|
||||
})
|
||||
|
||||
def set_full_name(self):
|
||||
self.lead_name = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name]))
|
||||
if self.first_name:
|
||||
self.lead_name = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name]))
|
||||
|
||||
def validate_email_id(self):
|
||||
if self.email_id:
|
||||
|
@ -2,8 +2,8 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('LinkedIn Settings', {
|
||||
onload: function(frm){
|
||||
if (frm.doc.session_status == 'Expired' && frm.doc.consumer_key && frm.doc.consumer_secret){
|
||||
onload: function(frm) {
|
||||
if (frm.doc.session_status == 'Expired' && frm.doc.consumer_key && frm.doc.consumer_secret) {
|
||||
frappe.confirm(
|
||||
__('Session not valid, Do you want to login?'),
|
||||
function(){
|
||||
@ -14,8 +14,9 @@ frappe.ui.form.on('LinkedIn Settings', {
|
||||
}
|
||||
);
|
||||
}
|
||||
frm.dashboard.set_headline(__("For more information, {0}.", [`<a target='_blank' href='https://docs.erpnext.com/docs/user/manual/en/CRM/linkedin-settings'>${__('Click here')}</a>`]));
|
||||
},
|
||||
refresh: function(frm){
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.session_status=="Expired"){
|
||||
let msg = __("Session Not Active. Save doc to login.");
|
||||
frm.dashboard.set_headline_alert(
|
||||
@ -53,7 +54,7 @@ frappe.ui.form.on('LinkedIn Settings', {
|
||||
);
|
||||
}
|
||||
},
|
||||
login: function(frm){
|
||||
login: function(frm) {
|
||||
if (frm.doc.consumer_key && frm.doc.consumer_secret){
|
||||
frappe.dom.freeze();
|
||||
frappe.call({
|
||||
@ -67,7 +68,7 @@ frappe.ui.form.on('LinkedIn Settings', {
|
||||
});
|
||||
}
|
||||
},
|
||||
after_save: function(frm){
|
||||
after_save: function(frm) {
|
||||
frm.trigger("login");
|
||||
}
|
||||
});
|
||||
|
@ -2,6 +2,7 @@
|
||||
"actions": [],
|
||||
"creation": "2020-01-30 13:36:39.492931",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.erpnext.com/docs/user/manual/en/CRM/linkedin-settings",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@ -87,7 +88,7 @@
|
||||
],
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-16 23:22:51.966397",
|
||||
"modified": "2021-02-18 15:19:21.920725",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "LinkedIn Settings",
|
||||
|
@ -3,11 +3,12 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, requests, json
|
||||
import frappe
|
||||
import requests
|
||||
from frappe import _
|
||||
from frappe.utils import get_site_url, get_url_to_form, get_link_to_form
|
||||
from frappe.utils import get_url_to_form
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.file_manager import get_file, get_file_path
|
||||
from frappe.utils.file_manager import get_file_path
|
||||
from six.moves.urllib.parse import urlencode
|
||||
|
||||
class LinkedInSettings(Document):
|
||||
@ -42,11 +43,7 @@ class LinkedInSettings(Document):
|
||||
self.db_set("access_token", response["access_token"])
|
||||
|
||||
def get_member_profile(self):
|
||||
headers = {
|
||||
"Authorization": "Bearer {}".format(self.access_token)
|
||||
}
|
||||
url = "https://api.linkedin.com/v2/me"
|
||||
response = requests.get(url=url, headers=headers)
|
||||
response = requests.get(url="https://api.linkedin.com/v2/me", headers=self.get_headers())
|
||||
response = frappe.parse_json(response.content.decode())
|
||||
|
||||
frappe.db.set_value(self.doctype, self.name, {
|
||||
@ -55,16 +52,16 @@ class LinkedInSettings(Document):
|
||||
"session_status": "Active"
|
||||
})
|
||||
frappe.local.response["type"] = "redirect"
|
||||
frappe.local.response["location"] = get_url_to_form("LinkedIn Settings","LinkedIn Settings")
|
||||
frappe.local.response["location"] = get_url_to_form("LinkedIn Settings", "LinkedIn Settings")
|
||||
|
||||
def post(self, text, media=None):
|
||||
def post(self, text, title, media=None):
|
||||
if not media:
|
||||
return self.post_text(text)
|
||||
return self.post_text(text, title)
|
||||
else:
|
||||
media_id = self.upload_image(media)
|
||||
|
||||
if media_id:
|
||||
return self.post_text(text, media_id=media_id)
|
||||
return self.post_text(text, title, media_id=media_id)
|
||||
else:
|
||||
frappe.log_error("Failed to upload media.","LinkedIn Upload Error")
|
||||
|
||||
@ -82,9 +79,7 @@ class LinkedInSettings(Document):
|
||||
}]
|
||||
}
|
||||
}
|
||||
headers = {
|
||||
"Authorization": "Bearer {}".format(self.access_token)
|
||||
}
|
||||
headers = self.get_headers()
|
||||
response = self.http_post(url=register_url, body=body, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
@ -100,24 +95,33 @@ class LinkedInSettings(Document):
|
||||
|
||||
return None
|
||||
|
||||
def post_text(self, text, media_id=None):
|
||||
def post_text(self, text, title, media_id=None):
|
||||
url = "https://api.linkedin.com/v2/shares"
|
||||
headers = {
|
||||
"X-Restli-Protocol-Version": "2.0.0",
|
||||
"Authorization": "Bearer {}".format(self.access_token),
|
||||
"Content-Type": "application/json; charset=UTF-8"
|
||||
}
|
||||
headers = self.get_headers()
|
||||
headers["X-Restli-Protocol-Version"] = "2.0.0"
|
||||
headers["Content-Type"] = "application/json; charset=UTF-8"
|
||||
|
||||
body = {
|
||||
"distribution": {
|
||||
"linkedInDistributionTarget": {}
|
||||
},
|
||||
"owner":"urn:li:organization:{0}".format(self.company_id),
|
||||
"subject": "Test Share Subject",
|
||||
"subject": title,
|
||||
"text": {
|
||||
"text": text
|
||||
}
|
||||
}
|
||||
|
||||
reference_url = self.get_reference_url(text)
|
||||
if reference_url:
|
||||
body["content"] = {
|
||||
"contentEntities": [
|
||||
{
|
||||
"entityLocation": reference_url
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
if media_id:
|
||||
body["content"]= {
|
||||
"contentEntities": [{
|
||||
@ -141,20 +145,60 @@ class LinkedInSettings(Document):
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
content = json.loads(response.content)
|
||||
|
||||
if response.status_code == 401:
|
||||
self.db_set("session_status", "Expired")
|
||||
frappe.db.commit()
|
||||
frappe.throw(content["message"], title="LinkedIn Error - Unauthorized")
|
||||
elif response.status_code == 403:
|
||||
frappe.msgprint(_("You Didn't have permission to access this API"))
|
||||
frappe.throw(content["message"], title="LinkedIn Error - Access Denied")
|
||||
else:
|
||||
frappe.throw(response.reason, title=response.status_code)
|
||||
self.api_error(response)
|
||||
|
||||
return response
|
||||
|
||||
def get_headers(self):
|
||||
return {
|
||||
"Authorization": "Bearer {}".format(self.access_token)
|
||||
}
|
||||
|
||||
def get_reference_url(self, text):
|
||||
import re
|
||||
regex_url = r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+"
|
||||
urls = re.findall(regex_url, text)
|
||||
if urls:
|
||||
return urls[0]
|
||||
|
||||
def delete_post(self, post_id):
|
||||
try:
|
||||
response = requests.delete(url="https://api.linkedin.com/v2/shares/urn:li:share:{0}".format(post_id), headers=self.get_headers())
|
||||
if response.status_code !=200:
|
||||
raise
|
||||
except Exception:
|
||||
self.api_error(response)
|
||||
|
||||
def get_post(self, post_id):
|
||||
url = "https://api.linkedin.com/v2/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=urn:li:organization:{0}&shares[0]=urn:li:share:{1}".format(self.company_id, post_id)
|
||||
|
||||
try:
|
||||
response = requests.get(url=url, headers=self.get_headers())
|
||||
if response.status_code !=200:
|
||||
raise
|
||||
|
||||
except Exception:
|
||||
self.api_error(response)
|
||||
|
||||
response = frappe.parse_json(response.content.decode())
|
||||
if len(response.elements):
|
||||
return response.elements[0]
|
||||
|
||||
return None
|
||||
|
||||
def api_error(self, response):
|
||||
content = frappe.parse_json(response.content.decode())
|
||||
|
||||
if response.status_code == 401:
|
||||
self.db_set("session_status", "Expired")
|
||||
frappe.db.commit()
|
||||
frappe.throw(content["message"], title=_("LinkedIn Error - Unauthorized"))
|
||||
elif response.status_code == 403:
|
||||
frappe.msgprint(_("You didn't have permission to access this API"))
|
||||
frappe.throw(content["message"], title=_("LinkedIn Error - Access Denied"))
|
||||
else:
|
||||
frappe.throw(response.reason, title=response.status_code)
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def callback(code=None, error=None, error_description=None):
|
||||
if not error:
|
||||
|
@ -1,67 +1,139 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
frappe.ui.form.on('Social Media Post', {
|
||||
validate: function(frm){
|
||||
if (frm.doc.twitter === 0 && frm.doc.linkedin === 0){
|
||||
frappe.throw(__("Select atleast one Social Media from Share on."))
|
||||
}
|
||||
if (frm.doc.scheduled_time) {
|
||||
let scheduled_time = new Date(frm.doc.scheduled_time);
|
||||
let date_time = new Date();
|
||||
if (scheduled_time.getTime() < date_time.getTime()){
|
||||
frappe.throw(__("Invalid Scheduled Time"));
|
||||
}
|
||||
}
|
||||
if (frm.doc.text?.length > 280){
|
||||
frappe.throw(__("Length Must be less than 280."))
|
||||
}
|
||||
},
|
||||
refresh: function(frm){
|
||||
if (frm.doc.docstatus === 1){
|
||||
if (frm.doc.post_status != "Posted"){
|
||||
add_post_btn(frm);
|
||||
}
|
||||
else if (frm.doc.post_status == "Posted"){
|
||||
frm.set_df_property('sheduled_time', 'read_only', 1);
|
||||
}
|
||||
validate: function(frm) {
|
||||
if (frm.doc.twitter === 0 && frm.doc.linkedin === 0) {
|
||||
frappe.throw(__("Select atleast one Social Media Platform to Share on."));
|
||||
}
|
||||
if (frm.doc.scheduled_time) {
|
||||
let scheduled_time = new Date(frm.doc.scheduled_time);
|
||||
let date_time = new Date();
|
||||
if (scheduled_time.getTime() < date_time.getTime()) {
|
||||
frappe.throw(__("Scheduled Time must be a future time."));
|
||||
}
|
||||
}
|
||||
frm.trigger('validate_tweet_length');
|
||||
},
|
||||
|
||||
let html='';
|
||||
if (frm.doc.twitter){
|
||||
let color = frm.doc.twitter_post_id ? "green" : "red";
|
||||
let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted";
|
||||
html += `<div class="col-xs-6">
|
||||
<span class="indicator whitespace-nowrap ${color}"><span>Twitter : ${status} </span></span>
|
||||
</div>` ;
|
||||
}
|
||||
if (frm.doc.linkedin){
|
||||
let color = frm.doc.linkedin_post_id ? "green" : "red";
|
||||
let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted";
|
||||
html += `<div class="col-xs-6">
|
||||
<span class="indicator whitespace-nowrap ${color}"><span>LinkedIn : ${status} </span></span>
|
||||
</div>` ;
|
||||
}
|
||||
html = `<div class="row">${html}</div>`;
|
||||
frm.dashboard.set_headline_alert(html);
|
||||
}
|
||||
}
|
||||
text: function(frm) {
|
||||
if (frm.doc.text) {
|
||||
frm.set_df_property('text', 'description', `${frm.doc.text.length}/280`);
|
||||
frm.refresh_field('text');
|
||||
frm.trigger('validate_tweet_length');
|
||||
}
|
||||
},
|
||||
|
||||
validate_tweet_length: function(frm) {
|
||||
if (frm.doc.text && frm.doc.text.length > 280) {
|
||||
frappe.throw(__("Tweet length Must be less than 280."));
|
||||
}
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
frm.trigger('make_dashboard');
|
||||
},
|
||||
|
||||
make_dashboard: function(frm) {
|
||||
if (frm.doc.post_status == "Posted") {
|
||||
frappe.call({
|
||||
doc: frm.doc,
|
||||
method: 'get_post',
|
||||
freeze: true,
|
||||
callback: (r) => {
|
||||
if (!r.message) {
|
||||
return;
|
||||
}
|
||||
|
||||
let datasets = [], colors = [];
|
||||
if (r.message && r.message.twitter) {
|
||||
colors.push('#1DA1F2');
|
||||
datasets.push({
|
||||
name: 'Twitter',
|
||||
values: [r.message.twitter.favorite_count, r.message.twitter.retweet_count]
|
||||
});
|
||||
}
|
||||
if (r.message && r.message.linkedin) {
|
||||
colors.push('#0077b5');
|
||||
datasets.push({
|
||||
name: 'LinkedIn',
|
||||
values: [r.message.linkedin.totalShareStatistics.likeCount, r.message.linkedin.totalShareStatistics.shareCount]
|
||||
});
|
||||
}
|
||||
|
||||
if (datasets.length) {
|
||||
frm.dashboard.render_graph({
|
||||
data: {
|
||||
labels: ['Likes', 'Retweets/Shares'],
|
||||
datasets: datasets
|
||||
},
|
||||
|
||||
title: __("Post Metrics"),
|
||||
type: 'bar',
|
||||
height: 300,
|
||||
colors: colors
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.trigger('text');
|
||||
|
||||
if (frm.doc.docstatus === 1) {
|
||||
if (!['Posted', 'Deleted'].includes(frm.doc.post_status)) {
|
||||
frm.trigger('add_post_btn');
|
||||
}
|
||||
if (frm.doc.post_status !='Deleted') {
|
||||
frm.add_custom_button(('Delete Post'), function() {
|
||||
frappe.confirm(__('Are you sure want to delete the Post from Social Media platforms?'),
|
||||
function() {
|
||||
frappe.call({
|
||||
doc: frm.doc,
|
||||
method: 'delete_post',
|
||||
freeze: true,
|
||||
callback: () => {
|
||||
frm.reload_doc();
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (frm.doc.post_status !='Deleted') {
|
||||
let html='';
|
||||
if (frm.doc.twitter) {
|
||||
let color = frm.doc.twitter_post_id ? "green" : "red";
|
||||
let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted";
|
||||
html += `<div class="col-xs-6">
|
||||
<span class="indicator whitespace-nowrap ${color}"><span>Twitter : ${status} </span></span>
|
||||
</div>` ;
|
||||
}
|
||||
if (frm.doc.linkedin) {
|
||||
let color = frm.doc.linkedin_post_id ? "green" : "red";
|
||||
let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted";
|
||||
html += `<div class="col-xs-6">
|
||||
<span class="indicator whitespace-nowrap ${color}"><span>LinkedIn : ${status} </span></span>
|
||||
</div>` ;
|
||||
}
|
||||
html = `<div class="row">${html}</div>`;
|
||||
frm.dashboard.set_headline_alert(html);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
add_post_btn: function(frm) {
|
||||
frm.add_custom_button(__('Post Now'), function() {
|
||||
frappe.call({
|
||||
doc: frm.doc,
|
||||
method: 'post',
|
||||
freeze: true,
|
||||
callback: function() {
|
||||
frm.reload_doc();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
var add_post_btn = function(frm){
|
||||
frm.add_custom_button(('Post Now'), function(){
|
||||
post(frm);
|
||||
});
|
||||
}
|
||||
var post = function(frm){
|
||||
frappe.dom.freeze();
|
||||
frappe.call({
|
||||
method: "erpnext.crm.doctype.social_media_post.social_media_post.publish",
|
||||
args: {
|
||||
doctype: frm.doc.doctype,
|
||||
name: frm.doc.name
|
||||
},
|
||||
callback: function(r) {
|
||||
frm.reload_doc();
|
||||
frappe.dom.unfreeze();
|
||||
}
|
||||
})
|
||||
|
||||
}
|
@ -3,9 +3,11 @@
|
||||
"autoname": "format: CRM-SMP-{YYYY}-{MM}-{DD}-{###}",
|
||||
"creation": "2020-01-30 11:53:13.872864",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.erpnext.com/docs/user/manual/en/CRM/social-media-post",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"campaign_name",
|
||||
"scheduled_time",
|
||||
"post_status",
|
||||
@ -30,32 +32,24 @@
|
||||
"fieldname": "text",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Tweet",
|
||||
"mandatory_depends_on": "eval:doc.twitter ==1",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"mandatory_depends_on": "eval:doc.twitter ==1"
|
||||
},
|
||||
{
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Image",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Image"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"default": "1",
|
||||
"fieldname": "twitter",
|
||||
"fieldtype": "Check",
|
||||
"label": "Twitter",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Twitter"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"default": "1",
|
||||
"fieldname": "linkedin",
|
||||
"fieldtype": "Check",
|
||||
"label": "LinkedIn",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "LinkedIn"
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
@ -64,27 +58,22 @@
|
||||
"no_copy": 1,
|
||||
"options": "Social Media Post",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.twitter ==1",
|
||||
"fieldname": "content",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Twitter",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Twitter"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "post_status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Post Status",
|
||||
"options": "\nScheduled\nPosted\nError",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"no_copy": 1,
|
||||
"options": "\nScheduled\nPosted\nCancelled\nDeleted\nError",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
@ -92,9 +81,8 @@
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Twitter Post Id",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
@ -102,82 +90,69 @@
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "LinkedIn Post Id",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "campaign_name",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Campaign",
|
||||
"options": "Campaign",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Campaign"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break",
|
||||
"label": "Share On",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Share On"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_14",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "tweet_preview",
|
||||
"fieldtype": "HTML",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "HTML"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"depends_on": "eval:doc.linkedin==1",
|
||||
"fieldname": "linkedin_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "LinkedIn",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "LinkedIn"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "attachments_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Attachments",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Attachments"
|
||||
},
|
||||
{
|
||||
"fieldname": "linkedin_post",
|
||||
"fieldtype": "Text",
|
||||
"label": "Post",
|
||||
"mandatory_depends_on": "eval:doc.linkedin ==1",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"mandatory_depends_on": "eval:doc.linkedin ==1"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_15",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "scheduled_time",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Scheduled Time",
|
||||
"read_only_depends_on": "eval:doc.post_status == \"Posted\"",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only_depends_on": "eval:doc.post_status == \"Posted\""
|
||||
},
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-14 10:31:33.961381",
|
||||
"modified": "2021-04-14 14:24:59.821223",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Social Media Post",
|
||||
@ -228,5 +203,6 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
@ -10,17 +10,51 @@ import datetime
|
||||
|
||||
class SocialMediaPost(Document):
|
||||
def validate(self):
|
||||
if (not self.twitter and not self.linkedin):
|
||||
frappe.throw(_("Select atleast one Social Media Platform to Share on."))
|
||||
|
||||
if self.scheduled_time:
|
||||
current_time = frappe.utils.now_datetime()
|
||||
scheduled_time = frappe.utils.get_datetime(self.scheduled_time)
|
||||
if scheduled_time < current_time:
|
||||
frappe.throw(_("Invalid Scheduled Time"))
|
||||
frappe.throw(_("Scheduled Time must be a future time."))
|
||||
|
||||
if self.text and len(self.text) > 280:
|
||||
frappe.throw(_("Tweet length must be less than 280."))
|
||||
|
||||
def submit(self):
|
||||
if self.scheduled_time:
|
||||
self.post_status = "Scheduled"
|
||||
super(SocialMediaPost, self).submit()
|
||||
|
||||
def on_cancel(self):
|
||||
self.db_set('post_status', 'Cancelled')
|
||||
|
||||
@frappe.whitelist()
|
||||
def delete_post(self):
|
||||
if self.twitter and self.twitter_post_id:
|
||||
twitter = frappe.get_doc("Twitter Settings")
|
||||
twitter.delete_tweet(self.twitter_post_id)
|
||||
|
||||
if self.linkedin and self.linkedin_post_id:
|
||||
linkedin = frappe.get_doc("LinkedIn Settings")
|
||||
linkedin.delete_post(self.linkedin_post_id)
|
||||
|
||||
self.db_set('post_status', 'Deleted')
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_post(self):
|
||||
response = {}
|
||||
if self.linkedin and self.linkedin_post_id:
|
||||
linkedin = frappe.get_doc("LinkedIn Settings")
|
||||
response['linkedin'] = linkedin.get_post(self.linkedin_post_id)
|
||||
if self.twitter and self.twitter_post_id:
|
||||
twitter = frappe.get_doc("Twitter Settings")
|
||||
response['twitter'] = twitter.get_tweet(self.twitter_post_id)
|
||||
|
||||
return response
|
||||
|
||||
@frappe.whitelist()
|
||||
def post(self):
|
||||
try:
|
||||
if self.twitter and not self.twitter_post_id:
|
||||
@ -29,28 +63,22 @@ class SocialMediaPost(Document):
|
||||
self.db_set("twitter_post_id", twitter_post.id)
|
||||
if self.linkedin and not self.linkedin_post_id:
|
||||
linkedin = frappe.get_doc("LinkedIn Settings")
|
||||
linkedin_post = linkedin.post(self.linkedin_post, self.image)
|
||||
self.db_set("linkedin_post_id", linkedin_post.headers['X-RestLi-Id'].split(":")[-1])
|
||||
linkedin_post = linkedin.post(self.linkedin_post, self.title, self.image)
|
||||
self.db_set("linkedin_post_id", linkedin_post.headers['X-RestLi-Id'])
|
||||
self.db_set("post_status", "Posted")
|
||||
|
||||
except:
|
||||
self.db_set("post_status", "Error")
|
||||
title = _("Error while POSTING {0}").format(self.name)
|
||||
traceback = frappe.get_traceback()
|
||||
frappe.log_error(message=traceback , title=title)
|
||||
frappe.log_error(message=frappe.get_traceback(), title=title)
|
||||
|
||||
def process_scheduled_social_media_posts():
|
||||
posts = frappe.get_list("Social Media Post", filters={"post_status": "Scheduled", "docstatus":1}, fields= ["name", "scheduled_time","post_status"])
|
||||
posts = frappe.get_list("Social Media Post", filters={"post_status": "Scheduled", "docstatus":1}, fields= ["name", "scheduled_time"])
|
||||
start = frappe.utils.now_datetime()
|
||||
end = start + datetime.timedelta(minutes=10)
|
||||
for post in posts:
|
||||
if post.scheduled_time:
|
||||
post_time = frappe.utils.get_datetime(post.scheduled_time)
|
||||
if post_time > start and post_time <= end:
|
||||
publish('Social Media Post', post.name)
|
||||
|
||||
@frappe.whitelist()
|
||||
def publish(doctype, name):
|
||||
sm_post = frappe.get_doc(doctype, name)
|
||||
sm_post.post()
|
||||
frappe.db.commit()
|
||||
sm_post = frappe.get_doc('Social Media Post', post.name)
|
||||
sm_post.post()
|
||||
|
@ -1,10 +1,11 @@
|
||||
frappe.listview_settings['Social Media Post'] = {
|
||||
add_fields: ["status","post_status"],
|
||||
get_indicator: function(doc) {
|
||||
return [__(doc.post_status), {
|
||||
"Scheduled": "orange",
|
||||
"Posted": "green",
|
||||
"Error": "red"
|
||||
}[doc.post_status]];
|
||||
}
|
||||
add_fields: ["status", "post_status"],
|
||||
get_indicator: function(doc) {
|
||||
return [__(doc.post_status), {
|
||||
"Scheduled": "orange",
|
||||
"Posted": "green",
|
||||
"Error": "red",
|
||||
"Deleted": "red"
|
||||
}[doc.post_status]];
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Twitter Settings', {
|
||||
onload: function(frm){
|
||||
onload: function(frm) {
|
||||
if (frm.doc.session_status == 'Expired' && frm.doc.consumer_key && frm.doc.consumer_secret){
|
||||
frappe.confirm(
|
||||
__('Session not valid, Do you want to login?'),
|
||||
@ -14,10 +14,11 @@ frappe.ui.form.on('Twitter Settings', {
|
||||
}
|
||||
);
|
||||
}
|
||||
frm.dashboard.set_headline(__("For more information, {0}.", [`<a target='_blank' href='https://docs.erpnext.com/docs/user/manual/en/CRM/twitter-settings'>${__('Click here')}</a>`]));
|
||||
},
|
||||
refresh: function(frm){
|
||||
refresh: function(frm) {
|
||||
let msg, color, flag=false;
|
||||
if (frm.doc.session_status == "Active"){
|
||||
if (frm.doc.session_status == "Active") {
|
||||
msg = __("Session Active");
|
||||
color = 'green';
|
||||
flag = true;
|
||||
@ -28,7 +29,7 @@ frappe.ui.form.on('Twitter Settings', {
|
||||
flag = true;
|
||||
}
|
||||
|
||||
if (flag){
|
||||
if (flag) {
|
||||
frm.dashboard.set_headline_alert(
|
||||
`<div class="row">
|
||||
<div class="col-xs-12">
|
||||
@ -38,7 +39,7 @@ frappe.ui.form.on('Twitter Settings', {
|
||||
);
|
||||
}
|
||||
},
|
||||
login: function(frm){
|
||||
login: function(frm) {
|
||||
if (frm.doc.consumer_key && frm.doc.consumer_secret){
|
||||
frappe.dom.freeze();
|
||||
frappe.call({
|
||||
@ -52,7 +53,7 @@ frappe.ui.form.on('Twitter Settings', {
|
||||
});
|
||||
}
|
||||
},
|
||||
after_save: function(frm){
|
||||
after_save: function(frm) {
|
||||
frm.trigger("login");
|
||||
}
|
||||
});
|
||||
|
@ -2,6 +2,7 @@
|
||||
"actions": [],
|
||||
"creation": "2020-01-30 10:29:08.562108",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.erpnext.com/docs/user/manual/en/CRM/twitter-settings",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@ -77,7 +78,7 @@
|
||||
"image_field": "profile_pic",
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-13 17:50:47.934776",
|
||||
"modified": "2021-02-18 15:18:07.900031",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Twitter Settings",
|
||||
|
@ -32,7 +32,9 @@ class TwitterSettings(Document):
|
||||
|
||||
try:
|
||||
auth.get_access_token(oauth_verifier)
|
||||
api = self.get_api(auth.access_token, auth.access_token_secret)
|
||||
self.access_token = auth.access_token
|
||||
self.access_token_secret = auth.access_token_secret
|
||||
api = self.get_api()
|
||||
user = api.me()
|
||||
profile_pic = (user._json["profile_image_url"]).replace("_normal","")
|
||||
|
||||
@ -50,11 +52,11 @@ class TwitterSettings(Document):
|
||||
frappe.msgprint(_("Error! Failed to get access token."))
|
||||
frappe.throw(_('Invalid Consumer Key or Consumer Secret Key'))
|
||||
|
||||
def get_api(self, access_token, access_token_secret):
|
||||
def get_api(self):
|
||||
# authentication of consumer key and secret
|
||||
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
|
||||
# authentication of access token and secret
|
||||
auth.set_access_token(access_token, access_token_secret)
|
||||
auth.set_access_token(self.access_token, self.access_token_secret)
|
||||
|
||||
return tweepy.API(auth)
|
||||
|
||||
@ -68,13 +70,13 @@ class TwitterSettings(Document):
|
||||
|
||||
def upload_image(self, media):
|
||||
media = get_file_path(media)
|
||||
api = self.get_api(self.access_token, self.access_token_secret)
|
||||
api = self.get_api()
|
||||
media = api.media_upload(media)
|
||||
|
||||
return media.media_id
|
||||
|
||||
def send_tweet(self, text, media_id=None):
|
||||
api = self.get_api(self.access_token, self.access_token_secret)
|
||||
api = self.get_api()
|
||||
try:
|
||||
if media_id:
|
||||
response = api.update_status(status = text, media_ids = [media_id])
|
||||
@ -84,12 +86,32 @@ class TwitterSettings(Document):
|
||||
return response
|
||||
|
||||
except TweepError as e:
|
||||
content = json.loads(e.response.content)
|
||||
content = content["errors"][0]
|
||||
if e.response.status_code == 401:
|
||||
self.db_set("session_status", "Expired")
|
||||
frappe.db.commit()
|
||||
frappe.throw(content["message"],title="Twitter Error {0} {1}".format(e.response.status_code, e.response.reason))
|
||||
self.api_error(e)
|
||||
|
||||
def delete_tweet(self, tweet_id):
|
||||
api = self.get_api()
|
||||
try:
|
||||
api.destroy_status(tweet_id)
|
||||
except TweepError as e:
|
||||
self.api_error(e)
|
||||
|
||||
def get_tweet(self, tweet_id):
|
||||
api = self.get_api()
|
||||
try:
|
||||
response = api.get_status(tweet_id, trim_user=True, include_entities=True)
|
||||
except TweepError as e:
|
||||
self.api_error(e)
|
||||
|
||||
return response._json
|
||||
|
||||
def api_error(self, e):
|
||||
content = json.loads(e.response.content)
|
||||
content = content["errors"][0]
|
||||
if e.response.status_code == 401:
|
||||
self.db_set("session_status", "Expired")
|
||||
frappe.db.commit()
|
||||
frappe.throw(content["message"],title=_("Twitter Error {0} : {1}").format(e.response.status_code, e.response.reason))
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def callback(oauth_token = None, oauth_verifier = None):
|
||||
|
@ -16,4 +16,3 @@ frappe.query_reports["Campaign Efficiency"] = {
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
@ -20,5 +20,3 @@ frappe.query_reports["Lead Conversion Time"] = {
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
|
@ -155,4 +155,3 @@ def get_conditions(filters) :
|
||||
conditions.append(" and `tabLead`.status=%(status)s")
|
||||
|
||||
return " ".join(conditions) if conditions else ""
|
||||
|
||||
|
@ -42,7 +42,3 @@ class AssessmentResult(Document):
|
||||
"student":self.student, "assessment_plan":self.assessment_plan, "docstatus":("!=", 2)})
|
||||
if assessment_result:
|
||||
frappe.throw(_("Assessment Result record {0} already exists.").format(getlink("Assessment Result",assessment_result[0].name)))
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -16,4 +16,3 @@ class TestAssessmentResult(unittest.TestCase):
|
||||
|
||||
grade = get_grade("_Test Grading Scale", 70)
|
||||
self.assertEqual("B", grade)
|
||||
|
@ -39,6 +39,3 @@ class TestCourseEnrollment(unittest.TestCase):
|
||||
doc = frappe.get_doc("Program Enrollment", entry.name)
|
||||
doc.cancel()
|
||||
doc.delete()
|
||||
|
||||
|
||||
|
||||
|
@ -47,4 +47,3 @@ class CourseSchedule(Document):
|
||||
|
||||
validate_overlap_for(self, "Assessment Plan", "room")
|
||||
validate_overlap_for(self, "Assessment Plan", "supervisor", self.instructor)
|
||||
|
||||
|
@ -174,4 +174,3 @@ def get_students(doctype, txt, searchfield, start, page_len, filters):
|
||||
tuple(students + ["%%%s%%" % txt, start, page_len]
|
||||
)
|
||||
)
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user