Add protocoin to Transaction model and editor
This commit is contained in:
parent
9d41af9eca
commit
81c9bd9c9b
|
@ -78,6 +78,7 @@ class Transaction(models.Model):
|
||||||
paypal_txn_id = models.CharField(max_length=17, blank=True, null=True, unique=True)
|
paypal_txn_id = models.CharField(max_length=17, blank=True, null=True, unique=True)
|
||||||
paypal_txn_type = models.CharField(max_length=64, blank=True, null=True)
|
paypal_txn_type = models.CharField(max_length=64, blank=True, null=True)
|
||||||
paypal_payer_id = models.CharField(max_length=13, blank=True, null=True)
|
paypal_payer_id = models.CharField(max_length=13, blank=True, null=True)
|
||||||
|
protocoin = models.DecimalField(max_digits=7, decimal_places=2)
|
||||||
|
|
||||||
report_type = models.TextField(blank=True, null=True)
|
report_type = models.TextField(blank=True, null=True)
|
||||||
report_memo = models.TextField(blank=True, null=True)
|
report_memo = models.TextField(blank=True, null=True)
|
||||||
|
|
|
@ -38,6 +38,7 @@ class TransactionSerializer(serializers.ModelSerializer):
|
||||||
'Member',
|
'Member',
|
||||||
'Clearing',
|
'Clearing',
|
||||||
'Cash',
|
'Cash',
|
||||||
|
'Protocoin',
|
||||||
])
|
])
|
||||||
category = serializers.ChoiceField([
|
category = serializers.ChoiceField([
|
||||||
'Membership',
|
'Membership',
|
||||||
|
@ -49,6 +50,7 @@ class TransactionSerializer(serializers.ModelSerializer):
|
||||||
'Garage Sale',
|
'Garage Sale',
|
||||||
'Reimburse',
|
'Reimburse',
|
||||||
'Other',
|
'Other',
|
||||||
|
'Exchange',
|
||||||
])
|
])
|
||||||
member_id = serializers.SerializerMethodField()
|
member_id = serializers.SerializerMethodField()
|
||||||
member_name = serializers.SerializerMethodField()
|
member_name = serializers.SerializerMethodField()
|
||||||
|
@ -58,8 +60,10 @@ class TransactionSerializer(serializers.ModelSerializer):
|
||||||
'Unmatched Purchase',
|
'Unmatched Purchase',
|
||||||
'User Flagged',
|
'User Flagged',
|
||||||
], allow_null=True, required=False)
|
], allow_null=True, required=False)
|
||||||
number_of_membership_months = serializers.IntegerField(max_value=36, min_value=-36)
|
number_of_membership_months = serializers.IntegerField(max_value=36, min_value=-36, default=0)
|
||||||
recorder = serializers.SerializerMethodField()
|
recorder = serializers.SerializerMethodField()
|
||||||
|
amount = serializers.DecimalField(max_digits=None, decimal_places=2, default=0)
|
||||||
|
protocoin = serializers.DecimalField(max_digits=None, decimal_places=2, default=0)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Transaction
|
model = models.Transaction
|
||||||
|
@ -80,18 +84,26 @@ class TransactionSerializer(serializers.ModelSerializer):
|
||||||
member = get_object_or_404(models.Member, id=self.initial_data['member_id'])
|
member = get_object_or_404(models.Member, id=self.initial_data['member_id'])
|
||||||
validated_data['user'] = member.user
|
validated_data['user'] = member.user
|
||||||
|
|
||||||
if validated_data['account_type'] != 'Clearing':
|
if validated_data['account_type'] == 'Protocoin' and validated_data['category'] == 'Exchange':
|
||||||
|
raise ValidationError(dict(category='Can\'t purchase Protocoin with Protocoin.'))
|
||||||
|
|
||||||
|
if validated_data['category'] == 'Exchange':
|
||||||
|
if validated_data['amount'] == 0:
|
||||||
|
raise ValidationError(dict(category='Can\'t purchase 0 Protocoin.'))
|
||||||
|
validated_data['protocoin'] = validated_data['amount']
|
||||||
|
|
||||||
|
if validated_data['account_type'] not in ['Clearing', 'Protocoin']:
|
||||||
if validated_data['amount'] == 0:
|
if validated_data['amount'] == 0:
|
||||||
raise ValidationError(dict(account_type='Can\'t have a $0.00 {} transaction. Do you want "Membership Adjustment"?'.format(validated_data['account_type'])))
|
raise ValidationError(dict(account_type='Can\'t have a $0.00 {} transaction. Do you want "Membership Adjustment"?'.format(validated_data['account_type'])))
|
||||||
|
|
||||||
if validated_data['category'] != 'Reimburse':
|
|
||||||
if validated_data['amount'] < 0:
|
|
||||||
raise ValidationError(dict(category='Can\'t have a negative {} transaction. Do you want "Reimbursement"?'.format(validated_data['category'])))
|
|
||||||
|
|
||||||
if validated_data['account_type'] == 'PayPal':
|
if validated_data['account_type'] == 'PayPal':
|
||||||
msg = 'Manual PayPal transaction added:\n' + str(validated_data)
|
msg = 'Manual PayPal transaction added:\n' + str(validated_data)
|
||||||
utils.alert_tanner(msg)
|
utils.alert_tanner(msg)
|
||||||
|
|
||||||
|
if validated_data['account_type'] == 'Protocoin':
|
||||||
|
msg = 'Manual Protocoin transaction added:\n' + str(validated_data)
|
||||||
|
utils.alert_tanner(msg)
|
||||||
|
|
||||||
if validated_data['account_type'] in ['Interac', 'Dream Pmt', 'Square Pmt', 'PayPal']:
|
if validated_data['account_type'] in ['Interac', 'Dream Pmt', 'Square Pmt', 'PayPal']:
|
||||||
if not validated_data.get('reference_number', None):
|
if not validated_data.get('reference_number', None):
|
||||||
raise ValidationError(dict(reference_number='This field is required.'))
|
raise ValidationError(dict(reference_number='This field is required.'))
|
||||||
|
|
|
@ -26,7 +26,7 @@ const memberSorts = {
|
||||||
export function MembersDropdown(props) {
|
export function MembersDropdown(props) {
|
||||||
const { token, name, onChange, value, initial } = props;
|
const { token, name, onChange, value, initial } = props;
|
||||||
const [response, setResponse] = useState({ results: [] });
|
const [response, setResponse] = useState({ results: [] });
|
||||||
const searchDefault = {seq: 0, q: initial || ''};
|
const searchDefault = {seq: 0, q: initial || '', sort: 'newest_active'};
|
||||||
const [search, setSearch] = useState(searchDefault);
|
const [search, setSearch] = useState(searchDefault);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -59,7 +59,7 @@ export function MembersDropdown(props) {
|
||||||
value={value}
|
value={value}
|
||||||
placeholder='Search for Member'
|
placeholder='Search for Member'
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onSearchChange={(e, v) => setSearch({seq: parseInt(e.timeStamp), q: v.searchQuery})}
|
onSearchChange={(e, v) => setSearch({seq: parseInt(e.timeStamp), q: v.searchQuery, sort: 'newest_active'})}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { isAdmin, BasicTable, requester } from './utils.js';
|
||||||
import { NotFound } from './Misc.js';
|
import { NotFound } from './Misc.js';
|
||||||
|
|
||||||
export function TransactionEditor(props) {
|
export function TransactionEditor(props) {
|
||||||
const { token, input, setInput, error, noMemberSearch } = props;
|
const { token, input, setInput, error } = props;
|
||||||
|
|
||||||
const [prevInput] = useState(input);
|
const [prevInput] = useState(input);
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ export function TransactionEditor(props) {
|
||||||
//{ key: '5', text: 'Member Balance / Protocash', value: 'Member' },
|
//{ key: '5', text: 'Member Balance / Protocash', value: 'Member' },
|
||||||
{ key: '6', text: 'Membership Adjustment / Clearing', value: 'Clearing' },
|
{ key: '6', text: 'Membership Adjustment / Clearing', value: 'Clearing' },
|
||||||
{ key: '7', text: 'PayPal', value: 'PayPal' },
|
{ key: '7', text: 'PayPal', value: 'PayPal' },
|
||||||
|
{ key: '8', text: 'Protocoin', value: 'Protocoin' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const sourceOptions = [
|
const sourceOptions = [
|
||||||
|
@ -53,17 +54,18 @@ export function TransactionEditor(props) {
|
||||||
{ key: '0', text: 'Membership Dues', value: 'Membership' },
|
{ key: '0', text: 'Membership Dues', value: 'Membership' },
|
||||||
{ key: '1', text: 'Course Fee', value: 'OnAcct' },
|
{ key: '1', text: 'Course Fee', value: 'OnAcct' },
|
||||||
{ key: '2', text: 'Snacks / Pop / Coffee', value: 'Snacks' },
|
{ key: '2', text: 'Snacks / Pop / Coffee', value: 'Snacks' },
|
||||||
{ key: '3', text: 'Donation', value: 'Donation' },
|
{ key: '3', text: 'Donation (Explain in Memo)', value: 'Donation' },
|
||||||
{ key: '4', text: 'Consumables (Explain in memo)', value: 'Consumables' },
|
{ key: '4', text: 'Consumables (Explain in Memo)', value: 'Consumables' },
|
||||||
{ key: '5', text: 'Purchase of Locker / Materials / Stock', value: 'Purchases' },
|
{ key: '5', text: 'Purchase of Locker / Materials / Stock', value: 'Purchases' },
|
||||||
//{ key: '6', text: 'Auction, Garage Sale, Nearly Free Shelf', value: 'Garage Sale' },
|
{ key: '6', text: 'Purchase of Protocoin', value: 'Exchange' },
|
||||||
{ key: '7', text: 'Reimbursement (Enter a negative value)', value: 'Reimburse' },
|
{ key: '7', text: 'Reimbursement (Not for Refunds)', value: 'Reimburse' },
|
||||||
{ key: '8', text: 'Other (Explain in memo)', value: 'Other' },
|
{ key: '8', text: 'Other (Explain in Memo)', value: 'Other' },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='transaction-editor'>
|
<div className='transaction-editor'>
|
||||||
{!noMemberSearch && <Form.Field error={error.member_id}>
|
<Form.Group widths='equal'>
|
||||||
|
<Form.Field error={error.member_id}>
|
||||||
<label>Member (search)</label>
|
<label>Member (search)</label>
|
||||||
<MembersDropdown
|
<MembersDropdown
|
||||||
token={token}
|
token={token}
|
||||||
|
@ -71,21 +73,54 @@ export function TransactionEditor(props) {
|
||||||
onChange={handleValues}
|
onChange={handleValues}
|
||||||
initial={input.member_name}
|
initial={input.member_name}
|
||||||
/>
|
/>
|
||||||
</Form.Field>}
|
</Form.Field>
|
||||||
|
|
||||||
<Form.Group widths='equal'>
|
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='Date'
|
label='Date'
|
||||||
fluid
|
fluid
|
||||||
{...makeProps('date')}
|
{...makeProps('date')}
|
||||||
/>
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Group widths='equal'>
|
||||||
|
<Form.Select
|
||||||
|
label='Payment Method'
|
||||||
|
fluid
|
||||||
|
options={accountOptions}
|
||||||
|
{...makeProps('account_type')}
|
||||||
|
onChange={handleValues}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{input.account_type && (input.account_type === 'Protocoin' ?
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='Amount'
|
label='Protocoin Delta (+/-)'
|
||||||
|
fluid
|
||||||
|
{...makeProps('protocoin')}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
<Form.Input
|
||||||
|
label='Amount ($)'
|
||||||
fluid
|
fluid
|
||||||
{...makeProps('amount')}
|
{...makeProps('amount')}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
|
||||||
|
{input?.account_type !== prevInput?.account_type && input?.account_type === 'PayPal' &&
|
||||||
|
<Message visible warning>
|
||||||
|
<Message.Header>Are you sure?</Message.Header>
|
||||||
|
<p>PayPal transactions are automatic. Double check there's no duplicate. They may take 24h to appear.</p>
|
||||||
|
</Message>
|
||||||
|
}
|
||||||
|
|
||||||
|
{input?.account_type !== prevInput?.account_type && input?.account_type === 'Protocoin' &&
|
||||||
|
<Message visible warning>
|
||||||
|
<Message.Header>Are you sure?</Message.Header>
|
||||||
|
<p>Protocoin spending transactions are automatic. Do you want "Purchase of Protocoin" category below?</p>
|
||||||
|
</Message>
|
||||||
|
}
|
||||||
|
|
||||||
|
<Form.Group widths='equal'>
|
||||||
<Form.Select
|
<Form.Select
|
||||||
label='Category'
|
label='Category'
|
||||||
fluid
|
fluid
|
||||||
|
@ -94,35 +129,22 @@ export function TransactionEditor(props) {
|
||||||
onChange={handleValues}
|
onChange={handleValues}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Form.Select
|
{input.category === 'Membership' &&
|
||||||
label='Payment Method / Account'
|
<Form.Input
|
||||||
|
label='Membership Months (+/-)'
|
||||||
fluid
|
fluid
|
||||||
options={accountOptions}
|
{...makeProps('number_of_membership_months')}
|
||||||
{...makeProps('account_type')}
|
|
||||||
onChange={handleValues}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{input?.account_type !== prevInput?.account_type && input?.account_type === 'PayPal' &&
|
|
||||||
<Message visible warning>
|
|
||||||
<Message.Header>Are you sure?</Message.Header>
|
|
||||||
<p>PayPal transactions should be automatic. Double check there's no duplicate. They may take 24h to appear.</p>
|
|
||||||
</Message>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{/* <Form.Group widths='equal'>
|
{input.category === 'Exchange' &&
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='Payment Method'
|
label='Protocoin Purchased'
|
||||||
fluid
|
fluid
|
||||||
{...makeProps('payment_method')}
|
{...makeProps('amount')} // trick the user
|
||||||
/>
|
/>
|
||||||
<Form.Select
|
}
|
||||||
label='Info Source'
|
</Form.Group>
|
||||||
fluid
|
|
||||||
options={sourceOptions}
|
|
||||||
{...makeProps('info_source')}
|
|
||||||
onChange={handleValues}
|
|
||||||
/>
|
|
||||||
</Form.Group> */}
|
|
||||||
|
|
||||||
<Form.Group widths='equal'>
|
<Form.Group widths='equal'>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
|
@ -131,18 +153,12 @@ export function TransactionEditor(props) {
|
||||||
{...makeProps('reference_number')}
|
{...makeProps('reference_number')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Form.Input
|
|
||||||
label='Number of Membership Months'
|
|
||||||
fluid
|
|
||||||
{...makeProps('number_of_membership_months')}
|
|
||||||
/>
|
|
||||||
</Form.Group>
|
|
||||||
|
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='Memo / Notes'
|
label='Memo / Notes'
|
||||||
fluid
|
fluid
|
||||||
{...makeProps('memo')}
|
{...makeProps('memo')}
|
||||||
/>
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -435,7 +451,7 @@ export function TransactionDetail(props) {
|
||||||
<Header size='large'>Transaction Receipt</Header>
|
<Header size='large'>Transaction Receipt</Header>
|
||||||
|
|
||||||
<Grid stackable columns={2}>
|
<Grid stackable columns={2}>
|
||||||
<Grid.Column>
|
<Grid.Column width={6}>
|
||||||
<TransactionTable user={user} transaction={transaction} />
|
<TransactionTable user={user} transaction={transaction} />
|
||||||
|
|
||||||
<div style={{ display: 'none' }}>
|
<div style={{ display: 'none' }}>
|
||||||
|
@ -447,7 +463,7 @@ export function TransactionDetail(props) {
|
||||||
/>
|
/>
|
||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
|
|
||||||
<Grid.Column>
|
<Grid.Column width={10}>
|
||||||
{isAdmin(user) ?
|
{isAdmin(user) ?
|
||||||
<Segment padded>
|
<Segment padded>
|
||||||
<EditTransaction transaction={transaction} setTransaction={setTransaction} {...props} />
|
<EditTransaction transaction={transaction} setTransaction={setTransaction} {...props} />
|
||||||
|
|
Loading…
Reference in New Issue
Block a user